lib/webdriver/promise.js

1// Licensed to the Software Freedom Conservancy (SFC) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The SFC licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18/**
19 * @license Portions of this code are from the Dojo toolkit, received under the
20 * BSD License:
21 * Redistribution and use in source and binary forms, with or without
22 * modification, are permitted provided that the following conditions are met:
23 *
24 * * Redistributions of source code must retain the above copyright notice,
25 * this list of conditions and the following disclaimer.
26 * * Redistributions in binary form must reproduce the above copyright notice,
27 * this list of conditions and the following disclaimer in the documentation
28 * and/or other materials provided with the distribution.
29 * * Neither the name of the Dojo Foundation nor the names of its contributors
30 * may be used to endorse or promote products derived from this software
31 * without specific prior written permission.
32 *
33 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
34 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
37 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
38 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
39 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
40 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
41 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
43 * POSSIBILITY OF SUCH DAMAGE.
44 */
45
46/**
47 * @fileoverview
48 * The promise module is centered around the
49 * {@linkplain webdriver.promise.ControlFlow ControlFlow}, a class that
50 * coordinates the execution of asynchronous tasks. The ControlFlow allows users
51 * to focus on the imperative commands for their script without worrying about
52 * chaining together every single asynchronous action, which can be tedious and
53 * verbose. APIs may be layered on top of the control flow to read as if they
54 * were synchronous. For instance, the core
55 * {@linkplain webdriver.WebDriver WebDriver} API is built on top of the
56 * control flow, allowing users to write
57 *
58 * driver.get('http://www.google.com/ncr');
59 * driver.findElement({name: 'q'}).sendKeys('webdriver');
60 * driver.findElement({name: 'btnGn'}).click();
61 *
62 * instead of
63 *
64 * driver.get('http://www.google.com/ncr')
65 * .then(function() {
66 * return driver.findElement({name: 'q'});
67 * })
68 * .then(function(q) {
69 * return q.sendKeys('webdriver');
70 * })
71 * .then(function() {
72 * return driver.findElement({name: 'btnG'});
73 * })
74 * .then(function(btnG) {
75 * return btnG.click();
76 * });
77 *
78 * ## Tasks and Task Queues
79 *
80 * The control flow is based on the concept of tasks and task queues. Tasks are
81 * functions that define the basic unit of work for the control flow to execute.
82 * Each task is scheduled via
83 * {@link webdriver.promise.ControlFlow#execute() ControlFlow#execute()}, which
84 * will return a {@link webdriver.promise.Promise Promise} that will be resolved
85 * with the task's result.
86 *
87 * A task queue contains all of the tasks scheduled within a single turn of the
88 * [JavaScript event loop][JSEL]. The control flow will create a new task queue
89 * the first time a task is scheduled within an event loop.
90 *
91 * var flow = promise.controlFlow();
92 * flow.execute(foo); // Creates a new task queue and inserts foo.
93 * flow.execute(bar); // Inserts bar into the same queue as foo.
94 * setTimeout(function() {
95 * flow.execute(baz); // Creates a new task queue and inserts baz.
96 * }, 0);
97 *
98 * Whenever the control flow creates a new task queue, it will automatically
99 * begin executing tasks in the next available turn of the event loop. This
100 * execution is scheduled using a "micro-task" timer, such as a (native)
101 * `Promise.then()` callback.
102 *
103 * setTimeout(() => console.log('a'));
104 * Promise.resolve().then(() => console.log('b')); // A native promise.
105 * flow.execute(() => console.log('c'));
106 * Promise.resolve().then(() => console.log('d'));
107 * setTimeout(() => console.log('fin'));
108 * // b
109 * // c
110 * // d
111 * // a
112 * // fin
113 *
114 * In the example above, b/c/d is logged before a/fin because native promises
115 * and this module use "micro-task" timers, which have a higher priority than
116 * "macro-tasks" like `setTimeout`.
117 *
118 * ## Task Execution
119 *
120 * Upon creating a task queue, and whenever an exisiting queue completes a task,
121 * the control flow will schedule a micro-task timer to process any scheduled
122 * tasks. This ensures no task is ever started within the same turn of the
123 * JavaScript event loop in which it was scheduled, nor is a task ever started
124 * within the same turn that another finishes.
125 *
126 * When the execution timer fires, a single task will be dequeued and executed.
127 * There are several important events that may occur while executing a task
128 * function:
129 *
130 * 1. A new task queue is created by a call to
131 * {@link webdriver.promise.ControlFlow#execute ControlFlow#execute()}. Any
132 * tasks scheduled within this task queue are considered subtasks of the
133 * current task.
134 * 2. The task function throws an error. Any scheduled tasks are immediately
135 * discarded and the task's promised result (previously returned by
136 * {@link webdriver.promise.ControlFlow#execute ControlFlow#execute()}) is
137 * immediately rejected with the thrown error.
138 * 3. The task function returns sucessfully.
139 *
140 * If a task function created a new task queue, the control flow will wait for
141 * that queue to complete before processing the task result. If the queue
142 * completes without error, the flow will settle the task's promise with the
143 * value originaly returned by the task function. On the other hand, if the task
144 * queue termintes with an error, the task's promise will be rejected with that
145 * error.
146 *
147 * flow.execute(function() {
148 * flow.execute(() => console.log('a'));
149 * flow.execute(() => console.log('b'));
150 * });
151 * flow.execute(() => console.log('c'));
152 * // a
153 * // b
154 * // c
155 *
156 * ## Promise Integration
157 *
158 * In addition to the {@link webdriver.promise.ControlFlow ControlFlow} class,
159 * the promise module also exports a [Promise/A+]
160 * {@linkplain webdriver.promise.Promise implementation} that is deeply
161 * integrated with the ControlFlow. First and foremost, each promise
162 * {@linkplain webdriver.promise.Promise#then() callback} is scheduled with the
163 * control flow as a task. As a result, each callback is invoked in its own turn
164 * of the JavaScript event loop with its own task queue. If any tasks are
165 * scheduled within a callback, the callback's promised result will not be
166 * settled until the task queue has completed.
167 *
168 * promise.fulfilled().then(function() {
169 * flow.execute(function() {
170 * console.log('b');
171 * });
172 * }).then(() => console.log('a'));
173 * // b
174 * // a
175 *
176 * ### Scheduling Promise Callbacks <a id="scheduling_callbacks"></a>
177 *
178 * How callbacks are scheduled in the control flow depends on when they are
179 * attached to the promise. Callbacks attached to a _previously_ resolved
180 * promise are immediately enqueued as subtasks of the currently running task.
181 *
182 * var p = promise.fulfilled();
183 * flow.execute(function() {
184 * flow.execute(() => console.log('A'));
185 * p.then( () => console.log('B'));
186 * flow.execute(() => console.log('C'));
187 * p.then( () => console.log('D'));
188 * }).then(function() {
189 * console.log('fin');
190 * });
191 * // A
192 * // B
193 * // C
194 * // D
195 * // fin
196 *
197 * When a promise is resolved while a task function is on the call stack, any
198 * callbacks also registered in that stack frame are scheduled as if the promise
199 * were already resolved:
200 *
201 * var d = promise.defer();
202 * flow.execute(function() {
203 * flow.execute( () => console.log('A'));
204 * d.promise.then(() => console.log('B'));
205 * flow.execute( () => console.log('C'));
206 * d.promise.then(() => console.log('D'));
207 *
208 * d.fulfill();
209 * }).then(function() {
210 * console.log('fin');
211 * });
212 * // A
213 * // B
214 * // C
215 * // D
216 * // fin
217 *
218 * If a promise is resolved while a task function is on the call stack, any
219 * previously registered callbacks (i.e. attached while the task was _not_ on
220 * the call stack), act as _interrupts_ and are inserted at the front of the
221 * task queue. If multiple promises are fulfilled, their interrupts are enqueued
222 * in the order the promises are resolved.
223 *
224 * var d1 = promise.defer();
225 * d1.promise.then(() => console.log('A'));
226 *
227 * var d2 = promise.defer();
228 * d2.promise.then(() => console.log('B'));
229 *
230 * flow.execute(function() {
231 * flow.execute(() => console.log('C'));
232 * flow.execute(() => console.log('D'));
233 * d1.fulfill();
234 * d2.fulfill();
235 * }).then(function() {
236 * console.log('fin');
237 * });
238 * // A
239 * // B
240 * // C
241 * // D
242 * // fin
243 *
244 * Within a task function (or callback), each step of a promise chain acts as
245 * an interrupt on the task queue:
246 *
247 * var d = promise.defer();
248 * flow.execute(function() {
249 * d.promise.
250 * then(() => console.log('A')).
251 * then(() => console.log('B')).
252 * then(() => console.log('C')).
253 * then(() => console.log('D'));
254 *
255 * flow.execute(() => console.log('E'));
256 * d.fulfill();
257 * }).then(function() {
258 * console.log('fin');
259 * });
260 * // A
261 * // B
262 * // C
263 * // D
264 * // E
265 * // fin
266 *
267 * If there are multiple promise chains derived from a single promise, they are
268 * processed in the order created:
269 *
270 * var d = promise.defer();
271 * flow.execute(function() {
272 * var chain = d.promise.then(() => console.log('A'));
273 *
274 * chain.then(() => console.log('B')).
275 * then(() => console.log('C'));
276 *
277 * chain.then(() => console.log('D')).
278 * then(() => console.log('E'));
279 *
280 * flow.execute(() => console.log('F'));
281 *
282 * d.fulfill();
283 * }).then(function() {
284 * console.log('fin');
285 * });
286 * // A
287 * // B
288 * // C
289 * // D
290 * // E
291 * // F
292 * // fin
293 *
294 * Even though a subtask's promised result will never resolve while the task
295 * function is on the stack, it will be treated as a promise resolved within the
296 * task. In all other scenarios, a task's promise behaves just like a normal
297 * promise. In the sample below, `C/D` is loggged before `B` because the
298 * resolution of `subtask1` interrupts the flow of the enclosing task. Within
299 * the final subtask, `E/F` is logged in order because `subtask1` is a resolved
300 * promise when that task runs.
301 *
302 * flow.execute(function() {
303 * var subtask1 = flow.execute(() => console.log('A'));
304 * var subtask2 = flow.execute(() => console.log('B'));
305 *
306 * subtask1.then(() => console.log('C'));
307 * subtask1.then(() => console.log('D'));
308 *
309 * flow.execute(function() {
310 * flow.execute(() => console.log('E'));
311 * subtask1.then(() => console.log('F'));
312 * });
313 * }).then(function() {
314 * console.log('fin');
315 * });
316 * // A
317 * // C
318 * // D
319 * // B
320 * // E
321 * // F
322 * // fin
323 *
324 * __Note__: while the ControlFlow will wait for
325 * {@linkplain webdriver.promise.ControlFlow#execute tasks} and
326 * {@linkplain webdriver.promise.Promise#then callbacks} to complete, it
327 * _will not_ wait for unresolved promises created within a task:
328 *
329 * flow.execute(function() {
330 * var p = new promise.Promise(function(fulfill) {
331 * setTimeout(fulfill, 100);
332 * });
333 * p.then(() => console.log('promise resolved!'));
334 * flow.execute(() => console.log('sub-task!'));
335 * }).then(function() {
336 * console.log('task complete!');
337 * });
338 * // sub-task!
339 * // task complete!
340 * // promise resolved!
341 *
342 * Finally, consider the following:
343 *
344 * var d = promise.defer();
345 * d.promise.then(() => console.log('A'));
346 * d.promise.then(() => console.log('B'));
347 *
348 * flow.execute(function() {
349 * flow.execute( () => console.log('C'));
350 * d.promise.then(() => console.log('D'));
351 *
352 * flow.execute( () => console.log('E'));
353 * d.promise.then(() => console.log('F'));
354 *
355 * d.fulfill();
356 *
357 * flow.execute( () => console.log('G'));
358 * d.promise.then(() => console.log('H'));
359 * }).then(function() {
360 * console.log('fin');
361 * });
362 * // A
363 * // B
364 * // C
365 * // D
366 * // E
367 * // F
368 * // G
369 * // H
370 * // fin
371 *
372 * In this example, callbacks are registered on `d.promise` both before and
373 * during the invocation of the task function. When `d.fulfill()` is called,
374 * the callbacks registered before the task (`A` & `B`) are registered as
375 * interrupts. The remaining callbacks were all attached within the task and
376 * are scheduled in the flow as standard tasks.
377 *
378 * ## Generator Support
379 *
380 * [Generators][GF] may be scheduled as tasks within a control flow or attached
381 * as callbacks to a promise. Each time the generator yields a promise, the
382 * control flow will wait for that promise to settle before executing the next
383 * iteration of the generator. The yielded promise's fulfilled value will be
384 * passed back into the generator:
385 *
386 * flow.execute(function* () {
387 * var d = promise.defer();
388 *
389 * setTimeout(() => console.log('...waiting...'), 25);
390 * setTimeout(() => d.fulfill(123), 50);
391 *
392 * console.log('start: ' + Date.now());
393 *
394 * var value = yield d.promise;
395 * console.log('mid: %d; value = %d', Date.now(), value);
396 *
397 * yield promise.delayed(10);
398 * console.log('end: ' + Date.now());
399 * }).then(function() {
400 * console.log('fin');
401 * });
402 * // start: 0
403 * // ...waiting...
404 * // mid: 50; value = 123
405 * // end: 60
406 * // fin
407 *
408 * Yielding the result of a promise chain will wait for the entire chain to
409 * complete:
410 *
411 * promise.fulfilled().then(function* () {
412 * console.log('start: ' + Date.now());
413 *
414 * var value = yield flow.
415 * execute(() => console.log('A')).
416 * then( () => console.log('B')).
417 * then( () => 123);
418 *
419 * console.log('mid: %s; value = %d', Date.now(), value);
420 *
421 * yield flow.execute(() => console.log('C'));
422 * }).then(function() {
423 * console.log('fin');
424 * });
425 * // start: 0
426 * // A
427 * // B
428 * // mid: 2; value = 123
429 * // C
430 * // fin
431 *
432 * Yielding a _rejected_ promise will cause the rejected value to be thrown
433 * within the generator function:
434 *
435 * flow.execute(function* () {
436 * console.log('start: ' + Date.now());
437 * try {
438 * yield promise.delayed(10).then(function() {
439 * throw Error('boom');
440 * });
441 * } catch (ex) {
442 * console.log('caught time: ' + Date.now());
443 * console.log(ex.message);
444 * }
445 * });
446 * // start: 0
447 * // caught time: 10
448 * // boom
449 *
450 * # Error Handling
451 *
452 * ES6 promises do not require users to handle a promise rejections. This can
453 * result in subtle bugs as the rejections are silently "swallowed" by the
454 * Promise class.
455 *
456 * Promise.reject(Error('boom'));
457 * // ... *crickets* ...
458 *
459 * Selenium's {@link webdriver.promise promise} module, on the other hand,
460 * requires that every rejection be explicitly handled. When a
461 * {@linkplain webdriver.promise.Promise Promise} is rejected and no callbacks
462 * are defined on that promise, it is considered an _unhandled rejection_ and
463 * reproted to the active task queue. If the rejection remains unhandled after
464 * a single turn of the [event loop][JSEL] (scheduled with a micro-task), it
465 * will propagate up the stack.
466 *
467 * ## Error Propagation
468 *
469 * If an unhandled rejection occurs within a task function, that task's promised
470 * result is rejected and all remaining subtasks are discarded:
471 *
472 * flow.execute(function() {
473 * // No callbacks registered on promise -> unhandled rejection
474 * promise.rejected(Error('boom'));
475 * flow.execute(function() { console.log('this will never run'); });
476 * }).thenCatch(function(e) {
477 * console.log(e.message);
478 * });
479 * // boom
480 *
481 * The promised results for discarded tasks are silently rejected with a
482 * cancellation error and existing callback chains will never fire.
483 *
484 * flow.execute(function() {
485 * promise.rejected(Error('boom'));
486 * flow.execute(function() { console.log('a'); }).
487 * then(function() { console.log('b'); });
488 * }).thenCatch(function(e) {
489 * console.log(e.message);
490 * });
491 * // boom
492 *
493 * An unhandled rejection takes precedence over a task function's returned
494 * result, even if that value is another promise:
495 *
496 * flow.execute(function() {
497 * promise.rejected(Error('boom'));
498 * return flow.execute(someOtherTask);
499 * }).thenCatch(function(e) {
500 * console.log(e.message);
501 * });
502 * // boom
503 *
504 * If there are multiple unhandled rejections within a task, they are packaged
505 * in a {@link webdriver.promise.MultipleUnhandledRejectionError
506 * MultipleUnhandledRejectionError}, which has an `errors` property that is a
507 * `Set` of the recorded unhandled rejections:
508 *
509 * flow.execute(function() {
510 * promise.rejected(Error('boom1'));
511 * promise.rejected(Error('boom2'));
512 * }).thenCatch(function(ex) {
513 * console.log(ex instanceof promise.MultipleUnhandledRejectionError);
514 * for (var e of ex.errors) {
515 * console.log(e.message);
516 * }
517 * });
518 * // boom1
519 * // boom2
520 *
521 * When a subtask is discarded due to an unreported rejection in its parent
522 * frame, the existing callbacks on that task will never settle and the
523 * callbacks will not be invoked. If a new callback is attached ot the subtask
524 * _after_ it has been discarded, it is handled the same as adding a callback
525 * to a cancelled promise: the error-callback path is invoked. This behavior is
526 * intended to handle cases where the user saves a reference to a task promise,
527 * as illustrated below.
528 *
529 * var subTask;
530 * flow.execute(function() {
531 * promise.rejected(Error('boom'));
532 * subTask = flow.execute(function() {});
533 * }).thenCatch(function(e) {
534 * console.log(e.message);
535 * }).then(function() {
536 * return subTask.then(
537 * () => console.log('subtask success!'),
538 * (e) => console.log('subtask failed:\n' + e));
539 * });
540 * // boom
541 * // subtask failed:
542 * // DiscardedTaskError: Task was discarded due to a previous failure: boom
543 *
544 * When a subtask fails, its promised result is treated the same as any other
545 * promise: it must be handled within one turn of the rejection or the unhandled
546 * rejection is propagated to the parent task. This means users can catch errors
547 * from complex flows from the top level task:
548 *
549 * flow.execute(function() {
550 * flow.execute(function() {
551 * flow.execute(function() {
552 * throw Error('fail!');
553 * });
554 * });
555 * }).thenCatch(function(e) {
556 * console.log(e.message);
557 * });
558 * // fail!
559 *
560 * ## Unhandled Rejection Events
561 *
562 * When an unhandled rejection propagates to the root of the control flow, the
563 * flow will emit an __uncaughtException__ event. If no listeners are registered
564 * on the flow, the error will be rethrown to the global error handler: an
565 * __uncaughtException__ event from the
566 * [`process`](https://nodejs.org/api/process.html) object in node, or
567 * `window.onerror` when running in a browser.
568 *
569 * Bottom line: you __*must*__ handle rejected promises.
570 *
571 * # Promise/A+ Compatibility
572 *
573 * This `promise` module is compliant with the [Promise/A+][] specification
574 * except for sections `2.2.6.1` and `2.2.6.2`:
575 *
576 * >
577 * > - `then` may be called multiple times on the same promise.
578 * > - If/when `promise` is fulfilled, all respective `onFulfilled` callbacks
579 * > must execute in the order of their originating calls to `then`.
580 * > - If/when `promise` is rejected, all respective `onRejected` callbacks
581 * > must execute in the order of their originating calls to `then`.
582 * >
583 *
584 * Specifically, the conformance tests contains the following scenario (for
585 * brevity, only the fulfillment version is shown):
586 *
587 * var p1 = Promise.resolve();
588 * p1.then(function() {
589 * console.log('A');
590 * p1.then(() => console.log('B'));
591 * });
592 * p1.then(() => console.log('C'));
593 * // A
594 * // C
595 * // B
596 *
597 * Since the [ControlFlow](#scheduling_callbacks) executes promise callbacks as
598 * tasks, with this module, the result would be
599 *
600 * var p2 = promise.fulfilled();
601 * p2.then(function() {
602 * console.log('A');
603 * p2.then(() => console.log('B');
604 * });
605 * p2.then(() => console.log('C'));
606 * // A
607 * // B
608 * // C
609 *
610 * [JSEL]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
611 * [GF]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
612 * [Promise/A+]: https://promisesaplus.com/
613 */
614
615goog.module('webdriver.promise');
616goog.module.declareLegacyNamespace();
617
618var Arrays = goog.require('goog.array');
619var asserts = goog.require('goog.asserts');
620var asyncRun = goog.require('goog.async.run');
621var throwException = goog.require('goog.async.throwException');
622var DebugError = goog.require('goog.debug.Error');
623var log = goog.require('goog.log');
624var Objects = goog.require('goog.object');
625var EventEmitter = goog.require('webdriver.EventEmitter');
626var stacktrace = goog.require('webdriver.stacktrace');
627
628
629
630/**
631 * @define {boolean} Whether to append traces of {@code then} to rejection
632 * errors.
633 */
634goog.define('webdriver.promise.LONG_STACK_TRACES', false);
635
636/** @const */
637var promise = exports;
638
639
640/** @const */
641var LOG = log.getLogger('webdriver.promise');
642
643
644/**
645 * @param {number} level What level of verbosity to log with.
646 * @param {(string|function(this: T): string)} loggable The message to log.
647 * @param {T=} opt_self The object in whose context to run the loggable
648 * function.
649 * @template T
650 */
651function vlog(level, loggable, opt_self) {
652 var logLevel = log.Level.FINE;
653 if (level > 1) {
654 logLevel = log.Level.FINEST;
655 } else if (level > 0) {
656 logLevel = log.Level.FINER;
657 }
658
659 if (typeof loggable === 'function') {
660 loggable = loggable.bind(opt_self);
661 }
662
663 log.log(LOG, logLevel, loggable);
664}
665
666
667/**
668 * Generates an error to capture the current stack trace.
669 * @param {string} name Error name for this stack trace.
670 * @param {string} msg Message to record.
671 * @param {!Function} topFn The function that should appear at the top of the
672 * stack; only applicable in V8.
673 * @return {!Error} The generated error.
674 */
675promise.captureStackTrace = function(name, msg, topFn) {
676 var e = Error(msg);
677 e.name = name;
678 if (Error.captureStackTrace) {
679 Error.captureStackTrace(e, topFn);
680 } else {
681 var stack = stacktrace.getStack(e);
682 e.stack = e.toString();
683 if (stack) {
684 e.stack += '\n' + stack;
685 }
686 }
687 return e;
688};
689
690
691/**
692 * Error used when the computation of a promise is cancelled.
693 *
694 * @unrestricted
695 */
696promise.CancellationError = goog.defineClass(DebugError, {
697 /**
698 * @param {string=} opt_msg The cancellation message.
699 */
700 constructor: function(opt_msg) {
701 promise.CancellationError.base(this, 'constructor', opt_msg);
702
703 /** @override */
704 this.name = 'CancellationError';
705
706 /** @private {boolean} */
707 this.silent_ = false;
708 },
709
710 statics: {
711 /**
712 * Wraps the given error in a CancellationError.
713 *
714 * @param {*} error The error to wrap.
715 * @param {string=} opt_msg The prefix message to use.
716 * @return {!promise.CancellationError} A cancellation error.
717 */
718 wrap: function(error, opt_msg) {
719 var message;
720 if (error instanceof promise.CancellationError) {
721 return new promise.CancellationError(
722 opt_msg ? (opt_msg + ': ' + error.message) : error.message);
723 } else if (opt_msg) {
724 message = opt_msg;
725 if (error) {
726 message += ': ' + error;
727 }
728 return new promise.CancellationError(message);
729 }
730 if (error) {
731 message = error + '';
732 }
733 return new promise.CancellationError(message);
734 }
735 }
736});
737
738
739/**
740 * Error used to cancel tasks when a control flow is reset.
741 * @unrestricted
742 * @final
743 */
744var FlowResetError = goog.defineClass(promise.CancellationError, {
745 constructor: function() {
746 FlowResetError.base(this, 'constructor', 'ControlFlow was reset');
747
748 /** @override */
749 this.name = 'FlowResetError';
750
751 this.silent_ = true;
752 }
753});
754
755
756/**
757 * Error used to cancel tasks that have been discarded due to an uncaught error
758 * reported earlier in the control flow.
759 * @unrestricted
760 * @final
761 */
762var DiscardedTaskError = goog.defineClass(promise.CancellationError, {
763 /** @param {*} error The original error. */
764 constructor: function(error) {
765 if (error instanceof DiscardedTaskError) {
766 return /** @type {!DiscardedTaskError} */(error);
767 }
768
769 var msg = '';
770 if (error) {
771 msg = ': ' + (typeof error.message === 'string' ? error.message : error);
772 }
773
774 DiscardedTaskError.base(this, 'constructor',
775 'Task was discarded due to a previous failure' + msg);
776
777 /** @override */
778 this.name = 'DiscardedTaskError';
779 this.silent_ = true;
780 }
781});
782
783
784/**
785 * Error used when there are multiple unhandled promise rejections detected
786 * within a task or callback.
787 *
788 * @unrestricted
789 * @final
790 */
791promise.MultipleUnhandledRejectionError = goog.defineClass(DebugError, {
792 /**
793 * @param {!(Set<*>)} errors The errors to report.
794 */
795 constructor: function(errors) {
796 promise.MultipleUnhandledRejectionError.base(
797 this, 'constructor', 'Multiple unhandled promise rejections reported');
798
799 /** @override */
800 this.name = 'MultipleUnhandledRejectionError';
801
802 /** @type {!Set<*>} */
803 this.errors = errors;
804 }
805});
806
807
808/**
809 * Property used to flag constructor's as implementing the Thenable interface
810 * for runtime type checking.
811 * @type {string}
812 * @const
813 */
814var IMPLEMENTED_BY_PROP = '$webdriver_Thenable';
815
816
817/**
818 * Thenable is a promise-like object with a {@code then} method which may be
819 * used to schedule callbacks on a promised value.
820 *
821 * @interface
822 * @extends {IThenable<T>}
823 * @template T
824 */
825promise.Thenable = goog.defineClass(null, {
826 statics: {
827 /**
828 * Adds a property to a class prototype to allow runtime checks of whether
829 * instances of that class implement the Thenable interface. This function
830 * will also ensure the prototype's {@code then} function is exported from
831 * compiled code.
832 * @param {function(new: promise.Thenable, ...?)} ctor The
833 * constructor whose prototype to modify.
834 */
835 addImplementation: function(ctor) {
836 // Based on goog.promise.Thenable.isImplementation.
837 ctor.prototype['then'] = ctor.prototype.then;
838 try {
839 // Old IE7 does not support defineProperty; IE8 only supports it for
840 // DOM elements.
841 Object.defineProperty(
842 ctor.prototype,
843 IMPLEMENTED_BY_PROP,
844 {'value': true, 'enumerable': false});
845 } catch (ex) {
846 ctor.prototype[IMPLEMENTED_BY_PROP] = true;
847 }
848 },
849
850 /**
851 * Checks if an object has been tagged for implementing the Thenable
852 * interface as defined by
853 * {@link webdriver.promise.Thenable.addImplementation}.
854 * @param {*} object The object to test.
855 * @return {boolean} Whether the object is an implementation of the Thenable
856 * interface.
857 */
858 isImplementation: function(object) {
859 // Based on goog.promise.Thenable.isImplementation.
860 if (!object) {
861 return false;
862 }
863 try {
864 return !!object[IMPLEMENTED_BY_PROP];
865 } catch (e) {
866 return false; // Property access seems to be forbidden.
867 }
868 }
869 },
870
871 /**
872 * Cancels the computation of this promise's value, rejecting the promise in
873 * the process. This method is a no-op if the promise has already been
874 * resolved.
875 *
876 * @param {(string|promise.CancellationError)=} opt_reason The reason this
877 * promise is being cancelled.
878 */
879 cancel: function(opt_reason) {},
880
881 /** @return {boolean} Whether this promise's value is still being computed. */
882 isPending: function() {},
883
884 /**
885 * Registers listeners for when this instance is resolved.
886 *
887 * @param {?(function(T): (R|IThenable<R>))=} opt_callback The
888 * function to call if this promise is successfully resolved. The function
889 * should expect a single argument: the promise's resolved value.
890 * @param {?(function(*): (R|IThenable<R>))=} opt_errback
891 * The function to call if this promise is rejected. The function should
892 * expect a single argument: the rejection reason.
893 * @return {!promise.Promise<R>} A new promise which will be
894 * resolved with the result of the invoked callback.
895 * @template R
896 */
897 then: function(opt_callback, opt_errback) {},
898
899 /**
900 * Registers a listener for when this promise is rejected. This is synonymous
901 * with the {@code catch} clause in a synchronous API:
902 *
903 * // Synchronous API:
904 * try {
905 * doSynchronousWork();
906 * } catch (ex) {
907 * console.error(ex);
908 * }
909 *
910 * // Asynchronous promise API:
911 * doAsynchronousWork().thenCatch(function(ex) {
912 * console.error(ex);
913 * });
914 *
915 * @param {function(*): (R|IThenable<R>)} errback The
916 * function to call if this promise is rejected. The function should
917 * expect a single argument: the rejection reason.
918 * @return {!promise.Promise<R>} A new promise which will be
919 * resolved with the result of the invoked callback.
920 * @template R
921 */
922 thenCatch: function(errback) {},
923
924 /**
925 * Registers a listener to invoke when this promise is resolved, regardless
926 * of whether the promise's value was successfully computed. This function
927 * is synonymous with the {@code finally} clause in a synchronous API:
928 *
929 * // Synchronous API:
930 * try {
931 * doSynchronousWork();
932 * } finally {
933 * cleanUp();
934 * }
935 *
936 * // Asynchronous promise API:
937 * doAsynchronousWork().thenFinally(cleanUp);
938 *
939 * __Note:__ similar to the {@code finally} clause, if the registered
940 * callback returns a rejected promise or throws an error, it will silently
941 * replace the rejection error (if any) from this promise:
942 *
943 * try {
944 * throw Error('one');
945 * } finally {
946 * throw Error('two'); // Hides Error: one
947 * }
948 *
949 * promise.rejected(Error('one'))
950 * .thenFinally(function() {
951 * throw Error('two'); // Hides Error: one
952 * });
953 *
954 * @param {function(): (R|IThenable<R>)} callback The function
955 * to call when this promise is resolved.
956 * @return {!promise.Promise<R>} A promise that will be fulfilled
957 * with the callback result.
958 * @template R
959 */
960 thenFinally: function(callback) {}
961});
962
963
964
965/**
966 * @enum {string}
967 */
968var PromiseState = {
969 PENDING: 'pending',
970 BLOCKED: 'blocked',
971 REJECTED: 'rejected',
972 FULFILLED: 'fulfilled'
973};
974
975
976/**
977 * Internal symbol used to store a cancellation handler for
978 * {@link promise.Promise} objects. This is an internal implementation detail
979 * used by the {@link TaskQueue} class to monitor for when a promise is
980 * cancelled without generating an extra promise via then().
981 */
982var CANCEL_HANDLER_SYMBOL = Symbol('on cancel');
983
984
985/**
986 * Represents the eventual value of a completed operation. Each promise may be
987 * in one of three states: pending, fulfilled, or rejected. Each promise starts
988 * in the pending state and may make a single transition to either a
989 * fulfilled or rejected state, at which point the promise is considered
990 * resolved.
991 *
992 * @implements {promise.Thenable<T>}
993 * @template T
994 * @see http://promises-aplus.github.io/promises-spec/
995 * @unrestricted // For using CANCEL_HANDLER_SYMBOL.
996 */
997promise.Promise = goog.defineClass(null, {
998 /**
999 * @param {function(
1000 * function((T|IThenable<T>|Thenable)=),
1001 * function(*=))} resolver
1002 * Function that is invoked immediately to begin computation of this
1003 * promise's value. The function should accept a pair of callback
1004 * functions, one for fulfilling the promise and another for rejecting it.
1005 * @param {promise.ControlFlow=} opt_flow The control flow
1006 * this instance was created under. Defaults to the currently active flow.
1007 */
1008 constructor: function(resolver, opt_flow) {
1009 goog.getUid(this);
1010
1011 /** @private {!promise.ControlFlow} */
1012 this.flow_ = opt_flow || promise.controlFlow();
1013
1014 /** @private {Error} */
1015 this.stack_ = null;
1016 if (promise.LONG_STACK_TRACES) {
1017 this.stack_ = promise.captureStackTrace(
1018 'Promise', 'new', promise.Promise);
1019 }
1020
1021 /** @private {promise.Promise<?>} */
1022 this.parent_ = null;
1023
1024 /** @private {Array<!Task>} */
1025 this.callbacks_ = null;
1026
1027 /** @private {PromiseState} */
1028 this.state_ = PromiseState.PENDING;
1029
1030 /** @private {boolean} */
1031 this.handled_ = false;
1032
1033 /** @private {*} */
1034 this.value_ = undefined;
1035
1036 /** @private {TaskQueue} */
1037 this.queue_ = null;
1038
1039 /** @private {(function(promise.CancellationError)|null)} */
1040 this[CANCEL_HANDLER_SYMBOL] = null;
1041
1042 try {
1043 var self = this;
1044 resolver(function(value) {
1045 self.resolve_(PromiseState.FULFILLED, value);
1046 }, function(reason) {
1047 self.resolve_(PromiseState.REJECTED, reason);
1048 });
1049 } catch (ex) {
1050 this.resolve_(PromiseState.REJECTED, ex);
1051 }
1052 },
1053
1054 /** @override */
1055 toString: function() {
1056 return 'Promise::' + goog.getUid(this) +
1057 ' {[[PromiseStatus]]: "' + this.state_ + '"}';
1058 },
1059
1060 /**
1061 * Resolves this promise. If the new value is itself a promise, this function
1062 * will wait for it to be resolved before notifying the registered listeners.
1063 * @param {PromiseState} newState The promise's new state.
1064 * @param {*} newValue The promise's new value.
1065 * @throws {TypeError} If {@code newValue === this}.
1066 * @private
1067 */
1068 resolve_: function(newState, newValue) {
1069 if (PromiseState.PENDING !== this.state_) {
1070 return;
1071 }
1072
1073 if (newValue === this) {
1074 // See promise a+, 2.3.1
1075 // http://promises-aplus.github.io/promises-spec/#point-48
1076 newValue = new TypeError('A promise may not resolve to itself');
1077 newState = PromiseState.REJECTED;
1078 }
1079
1080 this.parent_ = null;
1081 this.state_ = PromiseState.BLOCKED;
1082
1083 if (newState !== PromiseState.REJECTED) {
1084 if (promise.Thenable.isImplementation(newValue)) {
1085 // 2.3.2
1086 newValue = /** @type {!promise.Thenable} */(newValue);
1087 newValue.then(
1088 this.unblockAndResolve_.bind(this, PromiseState.FULFILLED),
1089 this.unblockAndResolve_.bind(this, PromiseState.REJECTED));
1090 return;
1091
1092 } else if (goog.isObject(newValue)) {
1093 // 2.3.3
1094
1095 try {
1096 // 2.3.3.1
1097 var then = newValue['then'];
1098 } catch (e) {
1099 // 2.3.3.2
1100 this.state_ = PromiseState.REJECTED;
1101 this.value_ = e;
1102 this.scheduleNotifications_();
1103 return;
1104 }
1105
1106 // NB: goog.isFunction is loose and will accept instanceof Function.
1107 if (typeof then === 'function') {
1108 // 2.3.3.3
1109 this.invokeThen_(newValue, then);
1110 return;
1111 }
1112 }
1113 }
1114
1115 if (newState === PromiseState.REJECTED &&
1116 isError(newValue) && newValue.stack && this.stack_) {
1117 newValue.stack += '\nFrom: ' + (this.stack_.stack || this.stack_);
1118 }
1119
1120 // 2.3.3.4 and 2.3.4
1121 this.state_ = newState;
1122 this.value_ = newValue;
1123 this.scheduleNotifications_();
1124 },
1125
1126 /**
1127 * Invokes a thenable's "then" method according to 2.3.3.3 of the promise
1128 * A+ spec.
1129 * @param {!Object} x The thenable object.
1130 * @param {!Function} then The "then" function to invoke.
1131 * @private
1132 */
1133 invokeThen_: function(x, then) {
1134 var called = false;
1135 var self = this;
1136
1137 var resolvePromise = function(value) {
1138 if (!called) { // 2.3.3.3.3
1139 called = true;
1140 // 2.3.3.3.1
1141 self.unblockAndResolve_(PromiseState.FULFILLED, value);
1142 }
1143 };
1144
1145 var rejectPromise = function(reason) {
1146 if (!called) { // 2.3.3.3.3
1147 called = true;
1148 // 2.3.3.3.2
1149 self.unblockAndResolve_(PromiseState.REJECTED, reason);
1150 }
1151 };
1152
1153 try {
1154 // 2.3.3.3
1155 then.call(x, resolvePromise, rejectPromise);
1156 } catch (e) {
1157 // 2.3.3.3.4.2
1158 rejectPromise(e);
1159 }
1160 },
1161
1162 /**
1163 * @param {PromiseState} newState The promise's new state.
1164 * @param {*} newValue The promise's new value.
1165 * @private
1166 */
1167 unblockAndResolve_: function(newState, newValue) {
1168 if (this.state_ === PromiseState.BLOCKED) {
1169 this.state_ = PromiseState.PENDING;
1170 this.resolve_(newState, newValue);
1171 }
1172 },
1173
1174 /**
1175 * @private
1176 */
1177 scheduleNotifications_: function() {
1178 vlog(2, () => this + ' scheduling notifications', this);
1179
1180 this[CANCEL_HANDLER_SYMBOL] = null;
1181 if (this.value_ instanceof promise.CancellationError
1182 && this.value_.silent_) {
1183 this.callbacks_ = null;
1184 }
1185
1186 if (!this.queue_) {
1187 this.queue_ = this.flow_.getActiveQueue_();
1188 }
1189
1190 if (!this.handled_ &&
1191 this.state_ === PromiseState.REJECTED &&
1192 !(this.value_ instanceof promise.CancellationError)) {
1193 this.queue_.addUnhandledRejection(this);
1194 }
1195 this.queue_.scheduleCallbacks(this);
1196 },
1197
1198 /** @override */
1199 cancel: function(opt_reason) {
1200 if (!canCancel(this)) {
1201 return;
1202 }
1203
1204 if (this.parent_ && canCancel(this.parent_)) {
1205 this.parent_.cancel(opt_reason);
1206 } else {
1207 var reason = promise.CancellationError.wrap(opt_reason);
1208 if (this[CANCEL_HANDLER_SYMBOL]) {
1209 this[CANCEL_HANDLER_SYMBOL](reason);
1210 this[CANCEL_HANDLER_SYMBOL] = null;
1211 }
1212
1213 if (this.state_ === PromiseState.BLOCKED) {
1214 this.unblockAndResolve_(PromiseState.REJECTED, reason);
1215 } else {
1216 this.resolve_(PromiseState.REJECTED, reason);
1217 }
1218 }
1219
1220 function canCancel(promise) {
1221 return promise.state_ === PromiseState.PENDING
1222 || promise.state_ === PromiseState.BLOCKED;
1223 }
1224 },
1225
1226 /** @override */
1227 isPending: function() {
1228 return this.state_ === PromiseState.PENDING;
1229 },
1230
1231 /** @override */
1232 then: function(opt_callback, opt_errback) {
1233 return this.addCallback_(
1234 opt_callback, opt_errback, 'then', promise.Promise.prototype.then);
1235 },
1236
1237 /** @override */
1238 thenCatch: function(errback) {
1239 return this.addCallback_(
1240 null, errback, 'thenCatch', promise.Promise.prototype.thenCatch);
1241 },
1242
1243 /** @override */
1244 thenFinally: function(callback) {
1245 var error;
1246 var mustThrow = false;
1247 return this.then(function() {
1248 return callback();
1249 }, function(err) {
1250 error = err;
1251 mustThrow = true;
1252 return callback();
1253 }).then(function() {
1254 if (mustThrow) {
1255 throw error;
1256 }
1257 });
1258 },
1259
1260 /**
1261 * Registers a new callback with this promise
1262 * @param {(function(T): (R|IThenable<R>)|null|undefined)} callback The
1263 * fulfillment callback.
1264 * @param {(function(*): (R|IThenable<R>)|null|undefined)} errback The
1265 * rejection callback.
1266 * @param {string} name The callback name.
1267 * @param {!Function} fn The function to use as the top of the stack when
1268 * recording the callback's creation point.
1269 * @return {!promise.Promise<R>} A new promise which will be resolved with the
1270 * esult of the invoked callback.
1271 * @template R
1272 * @private
1273 */
1274 addCallback_: function(callback, errback, name, fn) {
1275 if (!goog.isFunction(callback) && !goog.isFunction(errback)) {
1276 return this;
1277 }
1278
1279 this.handled_ = true;
1280 if (this.queue_) {
1281 this.queue_.clearUnhandledRejection(this);
1282 }
1283
1284 var cb = new Task(
1285 this.flow_,
1286 this.invokeCallback_.bind(this, callback, errback),
1287 name,
1288 promise.LONG_STACK_TRACES ? {name: 'Promise', top: fn} : undefined);
1289 cb.promise.parent_ = this;
1290
1291 if (this.state_ !== PromiseState.PENDING &&
1292 this.state_ !== PromiseState.BLOCKED) {
1293 this.flow_.getActiveQueue_().enqueue(cb);
1294 } else {
1295 if (!this.callbacks_) {
1296 this.callbacks_ = [];
1297 }
1298 this.callbacks_.push(cb);
1299 cb.blocked = true;
1300 this.flow_.getActiveQueue_().enqueue(cb);
1301 }
1302
1303 return cb.promise;
1304 },
1305
1306 /**
1307 * Invokes a callback function attached to this promise.
1308 * @param {(function(T): (R|IThenable<R>)|null|undefined)} callback The
1309 * fulfillment callback.
1310 * @param {(function(*): (R|IThenable<R>)|null|undefined)} errback The
1311 * rejection callback.
1312 * @template R
1313 * @private
1314 */
1315 invokeCallback_: function(callback, errback) {
1316 var callbackFn = callback;
1317 if (this.state_ === PromiseState.REJECTED) {
1318 callbackFn = errback;
1319 }
1320
1321 if (goog.isFunction(callbackFn)) {
1322 if (promise.isGenerator(callbackFn)) {
1323 return promise.consume(callbackFn, null, this.value_);
1324 }
1325 return callbackFn(this.value_);
1326 } else if (this.state_ === PromiseState.REJECTED) {
1327 throw this.value_;
1328 } else {
1329 return this.value_;
1330 }
1331 }
1332});
1333promise.Thenable.addImplementation(promise.Promise);
1334
1335
1336/**
1337 * Represents a value that will be resolved at some point in the future. This
1338 * class represents the protected "producer" half of a Promise - each Deferred
1339 * has a {@code promise} property that may be returned to consumers for
1340 * registering callbacks, reserving the ability to resolve the deferred to the
1341 * producer.
1342 *
1343 * If this Deferred is rejected and there are no listeners registered before
1344 * the next turn of the event loop, the rejection will be passed to the
1345 * {@link webdriver.promise.ControlFlow} as an unhandled failure.
1346 *
1347 * @implements {promise.Thenable<T>}
1348 * @template T
1349 */
1350promise.Deferred = goog.defineClass(null, {
1351 /**
1352 * @param {promise.ControlFlow=} opt_flow The control flow this instance was
1353 * created under. This should only be provided during unit tests.
1354 */
1355 constructor: function(opt_flow) {
1356 var fulfill, reject;
1357
1358 /** @type {!promise.Promise<T>} */
1359 this.promise = new promise.Promise(function(f, r) {
1360 fulfill = f;
1361 reject = r;
1362 }, opt_flow);
1363
1364 var self = this;
1365 var checkNotSelf = function(value) {
1366 if (value === self) {
1367 throw new TypeError('May not resolve a Deferred with itself');
1368 }
1369 };
1370
1371 /**
1372 * Resolves this deferred with the given value. It is safe to call this as a
1373 * normal function (with no bound "this").
1374 * @param {(T|IThenable<T>|Thenable)=} opt_value The fulfilled value.
1375 */
1376 this.fulfill = function(opt_value) {
1377 checkNotSelf(opt_value);
1378 fulfill(opt_value);
1379 };
1380
1381 /**
1382 * Rejects this promise with the given reason. It is safe to call this as a
1383 * normal function (with no bound "this").
1384 * @param {*=} opt_reason The rejection reason.
1385 */
1386 this.reject = function(opt_reason) {
1387 checkNotSelf(opt_reason);
1388 reject(opt_reason);
1389 };
1390 },
1391
1392 /** @override */
1393 isPending: function() {
1394 return this.promise.isPending();
1395 },
1396
1397 /** @override */
1398 cancel: function(opt_reason) {
1399 this.promise.cancel(opt_reason);
1400 },
1401
1402 /**
1403 * @override
1404 * @deprecated Use {@code then} from the promise property directly.
1405 */
1406 then: function(opt_cb, opt_eb) {
1407 return this.promise.then(opt_cb, opt_eb);
1408 },
1409
1410 /**
1411 * @override
1412 * @deprecated Use {@code thenCatch} from the promise property directly.
1413 */
1414 thenCatch: function(opt_eb) {
1415 return this.promise.thenCatch(opt_eb);
1416 },
1417
1418 /**
1419 * @override
1420 * @deprecated Use {@code thenFinally} from the promise property directly.
1421 */
1422 thenFinally: function(opt_cb) {
1423 return this.promise.thenFinally(opt_cb);
1424 }
1425});
1426promise.Thenable.addImplementation(promise.Deferred);
1427
1428
1429/**
1430 * Tests if a value is an Error-like object. This is more than an straight
1431 * instanceof check since the value may originate from another context.
1432 * @param {*} value The value to test.
1433 * @return {boolean} Whether the value is an error.
1434 */
1435function isError(value) {
1436 return value instanceof Error ||
1437 goog.isObject(value) &&
1438 (goog.isString(value.message) ||
1439 // A special test for goog.testing.JsUnitException.
1440 value.isJsUnitException);
1441
1442}
1443
1444
1445/**
1446 * Determines whether a {@code value} should be treated as a promise.
1447 * Any object whose "then" property is a function will be considered a promise.
1448 *
1449 * @param {*} value The value to test.
1450 * @return {boolean} Whether the value is a promise.
1451 */
1452promise.isPromise = function(value) {
1453 return !!value && goog.isObject(value) &&
1454 // Use array notation so the Closure compiler does not obfuscate away our
1455 // contract. Use typeof rather than goog.isFunction because
1456 // goog.isFunction accepts instanceof Function, which the promise spec
1457 // does not.
1458 typeof value['then'] === 'function';
1459};
1460
1461
1462/**
1463 * Creates a promise that will be resolved at a set time in the future.
1464 * @param {number} ms The amount of time, in milliseconds, to wait before
1465 * resolving the promise.
1466 * @return {!promise.Promise} The promise.
1467 */
1468promise.delayed = function(ms) {
1469 var key;
1470 return new promise.Promise(function(fulfill) {
1471 key = setTimeout(function() {
1472 key = null;
1473 fulfill();
1474 }, ms);
1475 }).thenCatch(function(e) {
1476 clearTimeout(key);
1477 key = null;
1478 throw e;
1479 });
1480};
1481
1482
1483/**
1484 * Creates a new deferred object.
1485 * @return {!promise.Deferred<T>} The new deferred object.
1486 * @template T
1487 */
1488promise.defer = function() {
1489 return new promise.Deferred();
1490};
1491
1492
1493/**
1494 * Creates a promise that has been resolved with the given value.
1495 * @param {T=} opt_value The resolved value.
1496 * @return {!promise.Promise<T>} The resolved promise.
1497 * @template T
1498 */
1499promise.fulfilled = function(opt_value) {
1500 if (opt_value instanceof promise.Promise) {
1501 return opt_value;
1502 }
1503 return new promise.Promise(function(fulfill) {
1504 fulfill(opt_value);
1505 });
1506};
1507
1508
1509/**
1510 * Creates a promise that has been rejected with the given reason.
1511 * @param {*=} opt_reason The rejection reason; may be any value, but is
1512 * usually an Error or a string.
1513 * @return {!promise.Promise<T>} The rejected promise.
1514 * @template T
1515 */
1516promise.rejected = function(opt_reason) {
1517 if (opt_reason instanceof promise.Promise) {
1518 return opt_reason;
1519 }
1520 return new promise.Promise(function(_, reject) {
1521 reject(opt_reason);
1522 });
1523};
1524
1525
1526/**
1527 * Wraps a function that expects a node-style callback as its final
1528 * argument. This callback expects two arguments: an error value (which will be
1529 * null if the call succeeded), and the success value as the second argument.
1530 * The callback will the resolve or reject the returned promise, based on its arguments.
1531 * @param {!Function} fn The function to wrap.
1532 * @param {...?} var_args The arguments to apply to the function, excluding the
1533 * final callback.
1534 * @return {!promise.Promise} A promise that will be resolved with the
1535 * result of the provided function's callback.
1536 */
1537promise.checkedNodeCall = function(fn, var_args) {
1538 var args = Arrays.slice(arguments, 1);
1539 return new promise.Promise(function(fulfill, reject) {
1540 try {
1541 args.push(function(error, value) {
1542 error ? reject(error) : fulfill(value);
1543 });
1544 fn.apply(undefined, args);
1545 } catch (ex) {
1546 reject(ex);
1547 }
1548 });
1549};
1550
1551
1552/**
1553 * Registers an observer on a promised {@code value}, returning a new promise
1554 * that will be resolved when the value is. If {@code value} is not a promise,
1555 * then the return promise will be immediately resolved.
1556 * @param {*} value The value to observe.
1557 * @param {Function=} opt_callback The function to call when the value is
1558 * resolved successfully.
1559 * @param {Function=} opt_errback The function to call when the value is
1560 * rejected.
1561 * @return {!promise.Promise} A new promise.
1562 */
1563promise.when = function(value, opt_callback, opt_errback) {
1564 if (promise.Thenable.isImplementation(value)) {
1565 return value.then(opt_callback, opt_errback);
1566 }
1567
1568 return new promise.Promise(function(fulfill, reject) {
1569 promise.asap(value, fulfill, reject);
1570 }).then(opt_callback, opt_errback);
1571};
1572
1573
1574/**
1575 * Invokes the appropriate callback function as soon as a promised
1576 * {@code value} is resolved. This function is similar to
1577 * {@link webdriver.promise.when}, except it does not return a new promise.
1578 * @param {*} value The value to observe.
1579 * @param {Function} callback The function to call when the value is
1580 * resolved successfully.
1581 * @param {Function=} opt_errback The function to call when the value is
1582 * rejected.
1583 */
1584promise.asap = function(value, callback, opt_errback) {
1585 if (promise.isPromise(value)) {
1586 value.then(callback, opt_errback);
1587
1588 // Maybe a Dojo-like deferred object?
1589 } else if (!!value && goog.isObject(value) &&
1590 goog.isFunction(value.addCallbacks)) {
1591 value.addCallbacks(callback, opt_errback);
1592
1593 // A raw value, return a resolved promise.
1594 } else if (callback) {
1595 callback(value);
1596 }
1597};
1598
1599
1600/**
1601 * Given an array of promises, will return a promise that will be fulfilled
1602 * with the fulfillment values of the input array's values. If any of the
1603 * input array's promises are rejected, the returned promise will be rejected
1604 * with the same reason.
1605 *
1606 * @param {!Array<(T|!promise.Promise<T>)>} arr An array of
1607 * promises to wait on.
1608 * @return {!promise.Promise<!Array<T>>} A promise that is
1609 * fulfilled with an array containing the fulfilled values of the
1610 * input array, or rejected with the same reason as the first
1611 * rejected value.
1612 * @template T
1613 */
1614promise.all = function(arr) {
1615 return new promise.Promise(function(fulfill, reject) {
1616 var n = arr.length;
1617 var values = [];
1618
1619 if (!n) {
1620 fulfill(values);
1621 return;
1622 }
1623
1624 var toFulfill = n;
1625 var onFulfilled = function(index, value) {
1626 values[index] = value;
1627 toFulfill--;
1628 if (toFulfill == 0) {
1629 fulfill(values);
1630 }
1631 };
1632
1633 for (var i = 0; i < n; ++i) {
1634 promise.asap(arr[i], goog.partial(onFulfilled, i), reject);
1635 }
1636 });
1637};
1638
1639
1640/**
1641 * Calls a function for each element in an array and inserts the result into a
1642 * new array, which is used as the fulfillment value of the promise returned
1643 * by this function.
1644 *
1645 * If the return value of the mapping function is a promise, this function
1646 * will wait for it to be fulfilled before inserting it into the new array.
1647 *
1648 * If the mapping function throws or returns a rejected promise, the
1649 * promise returned by this function will be rejected with the same reason.
1650 * Only the first failure will be reported; all subsequent errors will be
1651 * silently ignored.
1652 *
1653 * @param {!(Array<TYPE>|promise.Promise<!Array<TYPE>>)} arr The
1654 * array to iterator over, or a promise that will resolve to said array.
1655 * @param {function(this: SELF, TYPE, number, !Array<TYPE>): ?} fn The
1656 * function to call for each element in the array. This function should
1657 * expect three arguments (the element, the index, and the array itself.
1658 * @param {SELF=} opt_self The object to be used as the value of 'this' within
1659 * {@code fn}.
1660 * @template TYPE, SELF
1661 */
1662promise.map = function(arr, fn, opt_self) {
1663 return promise.fulfilled(arr).then(function(v) {
1664 goog.asserts.assertNumber(v.length, 'not an array like value');
1665 var arr = /** @type {!Array} */(v);
1666 return new promise.Promise(function(fulfill, reject) {
1667 var n = arr.length;
1668 var values = new Array(n);
1669 (function processNext(i) {
1670 for (; i < n; i++) {
1671 if (i in arr) {
1672 break;
1673 }
1674 }
1675 if (i >= n) {
1676 fulfill(values);
1677 return;
1678 }
1679 try {
1680 promise.asap(
1681 fn.call(opt_self, arr[i], i, /** @type {!Array} */(arr)),
1682 function(value) {
1683 values[i] = value;
1684 processNext(i + 1);
1685 },
1686 reject);
1687 } catch (ex) {
1688 reject(ex);
1689 }
1690 })(0);
1691 });
1692 });
1693};
1694
1695
1696/**
1697 * Calls a function for each element in an array, and if the function returns
1698 * true adds the element to a new array.
1699 *
1700 * If the return value of the filter function is a promise, this function
1701 * will wait for it to be fulfilled before determining whether to insert the
1702 * element into the new array.
1703 *
1704 * If the filter function throws or returns a rejected promise, the promise
1705 * returned by this function will be rejected with the same reason. Only the
1706 * first failure will be reported; all subsequent errors will be silently
1707 * ignored.
1708 *
1709 * @param {!(Array<TYPE>|promise.Promise<!Array<TYPE>>)} arr The
1710 * array to iterator over, or a promise that will resolve to said array.
1711 * @param {function(this: SELF, TYPE, number, !Array<TYPE>): (
1712 * boolean|promise.Promise<boolean>)} fn The function
1713 * to call for each element in the array.
1714 * @param {SELF=} opt_self The object to be used as the value of 'this' within
1715 * {@code fn}.
1716 * @template TYPE, SELF
1717 */
1718promise.filter = function(arr, fn, opt_self) {
1719 return promise.fulfilled(arr).then(function(v) {
1720 goog.asserts.assertNumber(v.length, 'not an array like value');
1721 var arr = /** @type {!Array} */(v);
1722 return new promise.Promise(function(fulfill, reject) {
1723 var n = arr.length;
1724 var values = [];
1725 var valuesLength = 0;
1726 (function processNext(i) {
1727 for (; i < n; i++) {
1728 if (i in arr) {
1729 break;
1730 }
1731 }
1732 if (i >= n) {
1733 fulfill(values);
1734 return;
1735 }
1736 try {
1737 var value = arr[i];
1738 var include = fn.call(opt_self, value, i, /** @type {!Array} */(arr));
1739 promise.asap(include, function(include) {
1740 if (include) {
1741 values[valuesLength++] = value;
1742 }
1743 processNext(i + 1);
1744 }, reject);
1745 } catch (ex) {
1746 reject(ex);
1747 }
1748 })(0);
1749 });
1750 });
1751};
1752
1753
1754/**
1755 * Returns a promise that will be resolved with the input value in a
1756 * fully-resolved state. If the value is an array, each element will be fully
1757 * resolved. Likewise, if the value is an object, all keys will be fully
1758 * resolved. In both cases, all nested arrays and objects will also be
1759 * fully resolved. All fields are resolved in place; the returned promise will
1760 * resolve on {@code value} and not a copy.
1761 *
1762 * Warning: This function makes no checks against objects that contain
1763 * cyclical references:
1764 *
1765 * var value = {};
1766 * value['self'] = value;
1767 * promise.fullyResolved(value); // Stack overflow.
1768 *
1769 * @param {*} value The value to fully resolve.
1770 * @return {!promise.Promise} A promise for a fully resolved version
1771 * of the input value.
1772 */
1773promise.fullyResolved = function(value) {
1774 if (promise.isPromise(value)) {
1775 return promise.when(value, fullyResolveValue);
1776 }
1777 return fullyResolveValue(value);
1778};
1779
1780
1781/**
1782 * @param {*} value The value to fully resolve. If a promise, assumed to
1783 * already be resolved.
1784 * @return {!promise.Promise} A promise for a fully resolved version
1785 * of the input value.
1786 */
1787 function fullyResolveValue(value) {
1788 switch (goog.typeOf(value)) {
1789 case 'array':
1790 return fullyResolveKeys(/** @type {!Array} */ (value));
1791
1792 case 'object':
1793 if (promise.isPromise(value)) {
1794 // We get here when the original input value is a promise that
1795 // resolves to itself. When the user provides us with such a promise,
1796 // trust that it counts as a "fully resolved" value and return it.
1797 // Of course, since it's already a promise, we can just return it
1798 // to the user instead of wrapping it in another promise.
1799 return /** @type {!promise.Promise} */ (value);
1800 }
1801
1802 if (goog.isNumber(value.nodeType) &&
1803 goog.isObject(value.ownerDocument) &&
1804 goog.isNumber(value.ownerDocument.nodeType)) {
1805 // DOM node; return early to avoid infinite recursion. Should we
1806 // only support objects with a certain level of nesting?
1807 return promise.fulfilled(value);
1808 }
1809
1810 return fullyResolveKeys(/** @type {!Object} */ (value));
1811
1812 default: // boolean, function, null, number, string, undefined
1813 return promise.fulfilled(value);
1814 }
1815}
1816
1817
1818/**
1819 * @param {!(Array|Object)} obj the object to resolve.
1820 * @return {!promise.Promise} A promise that will be resolved with the
1821 * input object once all of its values have been fully resolved.
1822 */
1823 function fullyResolveKeys(obj) {
1824 var isArray = goog.isArray(obj);
1825 var numKeys = isArray ? obj.length : Objects.getCount(obj);
1826 if (!numKeys) {
1827 return promise.fulfilled(obj);
1828 }
1829
1830 var numResolved = 0;
1831 return new promise.Promise(function(fulfill, reject) {
1832 // In pre-IE9, goog.array.forEach will not iterate properly over arrays
1833 // containing undefined values because "index in array" returns false
1834 // when array[index] === undefined (even for x = [undefined, 1]). To get
1835 // around this, we need to use our own forEach implementation.
1836 // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be
1837 // reproduced in IE9 by changing the browser/document modes, it requires an
1838 // actual pre-IE9 browser. Yay, IE!
1839 var forEachKey = !isArray ? Objects.forEach : function(arr, fn) {
1840 var n = arr.length;
1841 for (var i = 0; i < n; ++i) {
1842 fn.call(null, arr[i], i, arr);
1843 }
1844 };
1845
1846 forEachKey(obj, function(partialValue, key) {
1847 var type = goog.typeOf(partialValue);
1848 if (type != 'array' && type != 'object') {
1849 maybeResolveValue();
1850 return;
1851 }
1852
1853 promise.fullyResolved(partialValue).then(
1854 function(resolvedValue) {
1855 obj[key] = resolvedValue;
1856 maybeResolveValue();
1857 },
1858 reject);
1859 });
1860
1861 function maybeResolveValue() {
1862 if (++numResolved == numKeys) {
1863 fulfill(obj);
1864 }
1865 }
1866 });
1867}
1868
1869
1870//////////////////////////////////////////////////////////////////////////////
1871//
1872// promise.ControlFlow
1873//
1874//////////////////////////////////////////////////////////////////////////////
1875
1876
1877
1878/**
1879 * Handles the execution of scheduled tasks, each of which may be an
1880 * asynchronous operation. The control flow will ensure tasks are executed in
1881 * the ordered scheduled, starting each task only once those before it have
1882 * completed.
1883 *
1884 * Each task scheduled within this flow may return a
1885 * {@link webdriver.promise.Promise} to indicate it is an asynchronous
1886 * operation. The ControlFlow will wait for such promises to be resolved before
1887 * marking the task as completed.
1888 *
1889 * Tasks and each callback registered on a {@link webdriver.promise.Promise}
1890 * will be run in their own ControlFlow frame. Any tasks scheduled within a
1891 * frame will take priority over previously scheduled tasks. Furthermore, if any
1892 * of the tasks in the frame fail, the remainder of the tasks in that frame will
1893 * be discarded and the failure will be propagated to the user through the
1894 * callback/task's promised result.
1895 *
1896 * Each time a ControlFlow empties its task queue, it will fire an
1897 * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event. Conversely,
1898 * whenever the flow terminates due to an unhandled error, it will remove all
1899 * remaining tasks in its queue and fire an
1900 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION
1901 * UNCAUGHT_EXCEPTION} event. If there are no listeners registered with the
1902 * flow, the error will be rethrown to the global error handler.
1903 *
1904 * Refer to the {@link webdriver.promise} module documentation for a detailed
1905 * explanation of how the ControlFlow coordinates task execution.
1906 *
1907 * @final
1908 */
1909promise.ControlFlow = goog.defineClass(EventEmitter, {
1910 // TODO: remove this empty comment when the compiler properly handles
1911 // goog.defineClass with a missing constructor comment.
1912 /** @constructor */
1913 constructor: function() {
1914 promise.ControlFlow.base(this, 'constructor');
1915
1916 /** @private {boolean} */
1917 this.propagateUnhandledRejections_ = true;
1918
1919 /** @private {TaskQueue} */
1920 this.activeQueue_ = null;
1921
1922 /** @private {Set<TaskQueue>} */
1923 this.taskQueues_ = null;
1924
1925 /**
1926 * Micro task that controls shutting down the control flow. Upon shut down,
1927 * the flow will emit an
1928 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Idle events
1929 * always follow a brief timeout in order to catch latent errors from the
1930 * last completed task. If this task had a callback registered, but no
1931 * errback, and the task fails, the unhandled failure would not be reported
1932 * by the promise system until the next turn of the event loop:
1933 *
1934 * // Schedule 1 task that fails.
1935 * var result = promise.controlFlow().schedule('example',
1936 * function() { return promise.rejected('failed'); });
1937 * // Set a callback on the result. This delays reporting the unhandled
1938 * // failure for 1 turn of the event loop.
1939 * result.then(goog.nullFunction);
1940 *
1941 * @private {MicroTask}
1942 */
1943 this.shutdownTask_ = null;
1944
1945 /**
1946 * ID for a long running interval used to keep a Node.js process running
1947 * while a control flow's event loop is still working. This is a cheap hack
1948 * required since JS events are only scheduled to run when there is
1949 * _actually_ something to run. When a control flow is waiting on a task,
1950 * there will be nothing in the JS event loop and the process would
1951 * terminate without this.
1952 *
1953 * @private {?number}
1954 */
1955 this.hold_ = null;
1956 },
1957
1958 /**
1959 * Returns a string representation of this control flow, which is its current
1960 * {@link #getSchedule() schedule}, sans task stack traces.
1961 * @return {string} The string representation of this contorl flow.
1962 * @override
1963 */
1964 toString: function() {
1965 return this.getSchedule();
1966 },
1967
1968 /**
1969 * Sets whether any unhandled rejections should propagate up through the
1970 * control flow stack and cause rejections within parent tasks. If error
1971 * propagation is disabled, tasks will not be aborted when an unhandled
1972 * promise rejection is detected, but the rejection _will_ trigger an
1973 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION}
1974 * event.
1975 *
1976 * The default behavior is to propagate all unhandled rejections. _The use
1977 * of this option is highly discouraged._
1978 *
1979 * @param {boolean} propagate whether to propagate errors.
1980 */
1981 setPropagateUnhandledRejections: function(propagate) {
1982 this.propagateUnhandledRejections_ = propagate;
1983 },
1984
1985 /**
1986 * @return {boolean} Whether this flow is currently idle.
1987 */
1988 isIdle: function() {
1989 return !this.shutdownTask_ && (!this.taskQueues_ || !this.taskQueues_.size);
1990 },
1991
1992 /**
1993 * Resets this instance, clearing its queue and removing all event listeners.
1994 */
1995 reset: function() {
1996 this.cancelQueues_(new FlowResetError);
1997 this.emit(promise.ControlFlow.EventType.RESET);
1998 this.removeAllListeners();
1999 this.cancelShutdown_();
2000 },
2001
2002 /**
2003 * Generates an annotated string describing the internal state of this control
2004 * flow, including the currently executing as well as pending tasks. If
2005 * {@code opt_includeStackTraces === true}, the string will include the
2006 * stack trace from when each task was scheduled.
2007 * @param {string=} opt_includeStackTraces Whether to include the stack traces
2008 * from when each task was scheduled. Defaults to false.
2009 * @return {string} String representation of this flow's internal state.
2010 */
2011 getSchedule: function(opt_includeStackTraces) {
2012 var ret = 'ControlFlow::' + goog.getUid(this);
2013 var activeQueue = this.activeQueue_;
2014 if (!this.taskQueues_ || !this.taskQueues_.size) {
2015 return ret;
2016 }
2017 var childIndent = '| ';
2018 for (var q of this.taskQueues_) {
2019 ret += '\n' + printQ(q, childIndent);
2020 }
2021 return ret;
2022
2023 function printQ(q, indent) {
2024 var ret = q.toString();
2025 if (q === activeQueue) {
2026 ret = '(active) ' + ret;
2027 }
2028 var prefix = indent + childIndent;
2029 if (q.pending_) {
2030 if (q.pending_.q.state_ !== TaskQueueState.FINISHED) {
2031 ret += '\n' + prefix + '(pending) ' + q.pending_.task;
2032 ret += '\n' + printQ(q.pending_.q, prefix + childIndent);
2033 } else {
2034 ret += '\n' + prefix + '(blocked) ' + q.pending_.task;
2035 }
2036 }
2037 if (q.interrupts_) {
2038 q.interrupts_.forEach((task) => {
2039 ret += '\n' + prefix + task;
2040 });
2041 }
2042 if (q.tasks_) {
2043 q.tasks_.forEach((task) => ret += printTask(task, '\n' + prefix));
2044 }
2045 return indent + ret;
2046 }
2047
2048 function printTask(task, prefix) {
2049 var ret = prefix + task;
2050 if (opt_includeStackTraces && task.promise.stack_) {
2051 ret += prefix + childIndent
2052 + (task.promise.stack_.stack || task.promise.stack_)
2053 .replace(/\n/g, prefix);
2054 }
2055 return ret;
2056 }
2057 },
2058
2059 /**
2060 * Returns the currently actively task queue for this flow. If there is no
2061 * active queue, one will be created.
2062 * @return {!TaskQueue} the currently active task queue for this flow.
2063 * @private
2064 */
2065 getActiveQueue_: function() {
2066 if (this.activeQueue_) {
2067 return this.activeQueue_;
2068 }
2069
2070 this.activeQueue_ = new TaskQueue(this);
2071 if (!this.taskQueues_) {
2072 this.taskQueues_ = new Set();
2073 }
2074 this.taskQueues_.add(this.activeQueue_);
2075 this.activeQueue_
2076 .once('end', this.onQueueEnd_, this)
2077 .once('error', this.onQueueError_, this);
2078
2079 asyncRun(() => this.activeQueue_ = null, this);
2080 this.activeQueue_.start();
2081 return this.activeQueue_;
2082 },
2083
2084 /**
2085 * Schedules a task for execution. If there is nothing currently in the
2086 * queue, the task will be executed in the next turn of the event loop. If
2087 * the task function is a generator, the task will be executed using
2088 * {@link webdriver.promise.consume}.
2089 *
2090 * @param {function(): (T|promise.Promise<T>)} fn The function to
2091 * call to start the task. If the function returns a
2092 * {@link webdriver.promise.Promise}, this instance will wait for it to be
2093 * resolved before starting the next task.
2094 * @param {string=} opt_description A description of the task.
2095 * @return {!promise.Promise<T>} A promise that will be resolved
2096 * with the result of the action.
2097 * @template T
2098 */
2099 execute: function(fn, opt_description) {
2100 if (promise.isGenerator(fn)) {
2101 fn = goog.partial(promise.consume, fn);
2102 }
2103
2104 if (!this.hold_) {
2105 var holdIntervalMs = 2147483647; // 2^31-1; max timer length for Node.js
2106 this.hold_ = setInterval(goog.nullFunction, holdIntervalMs);
2107 }
2108
2109 var task = new Task(
2110 this, fn, opt_description || '<anonymous>',
2111 {name: 'Task', top: promise.ControlFlow.prototype.execute});
2112
2113 var q = this.getActiveQueue_();
2114 q.enqueue(task);
2115 this.emit(promise.ControlFlow.EventType.SCHEDULE_TASK, task.description);
2116 return task.promise;
2117 },
2118
2119 /**
2120 * Inserts a {@code setTimeout} into the command queue. This is equivalent to
2121 * a thread sleep in a synchronous programming language.
2122 *
2123 * @param {number} ms The timeout delay, in milliseconds.
2124 * @param {string=} opt_description A description to accompany the timeout.
2125 * @return {!promise.Promise} A promise that will be resolved with
2126 * the result of the action.
2127 */
2128 timeout: function(ms, opt_description) {
2129 return this.execute(function() {
2130 return promise.delayed(ms);
2131 }, opt_description);
2132 },
2133
2134 /**
2135 * Schedules a task that shall wait for a condition to hold. Each condition
2136 * function may return any value, but it will always be evaluated as a
2137 * boolean.
2138 *
2139 * Condition functions may schedule sub-tasks with this instance, however,
2140 * their execution time will be factored into whether a wait has timed out.
2141 *
2142 * In the event a condition returns a Promise, the polling loop will wait for
2143 * it to be resolved before evaluating whether the condition has been
2144 * satisfied. The resolution time for a promise is factored into whether a
2145 * wait has timed out.
2146 *
2147 * If the condition function throws, or returns a rejected promise, the
2148 * wait task will fail.
2149 *
2150 * If the condition is defined as a promise, the flow will wait for it to
2151 * settle. If the timeout expires before the promise settles, the promise
2152 * returned by this function will be rejected.
2153 *
2154 * If this function is invoked with `timeout === 0`, or the timeout is
2155 * omitted, the flow will wait indefinitely for the condition to be satisfied.
2156 *
2157 * @param {(!promise.Promise<T>|function())} condition The condition to poll,
2158 * or a promise to wait on.
2159 * @param {number=} opt_timeout How long to wait, in milliseconds, for the
2160 * condition to hold before timing out. If omitted, the flow will wait
2161 * indefinitely.
2162 * @param {string=} opt_message An optional error message to include if the
2163 * wait times out; defaults to the empty string.
2164 * @return {!promise.Promise<T>} A promise that will be fulfilled
2165 * when the condition has been satisified. The promise shall be rejected
2166 * if the wait times out waiting for the condition.
2167 * @throws {TypeError} If condition is not a function or promise or if timeout
2168 * is not a number >= 0.
2169 * @template T
2170 */
2171 wait: function(condition, opt_timeout, opt_message) {
2172 var timeout = opt_timeout || 0;
2173 if (!goog.isNumber(timeout) || timeout < 0) {
2174 throw TypeError('timeout must be a number >= 0: ' + timeout);
2175 }
2176
2177 if (promise.isPromise(condition)) {
2178 return this.execute(function() {
2179 if (!timeout) {
2180 return condition;
2181 }
2182 return new promise.Promise(function(fulfill, reject) {
2183 var start = goog.now();
2184 var timer = setTimeout(function() {
2185 timer = null;
2186 reject(Error((opt_message ? opt_message + '\n' : '') +
2187 'Timed out waiting for promise to resolve after ' +
2188 (goog.now() - start) + 'ms'));
2189 }, timeout);
2190
2191 /** @type {Thenable} */(condition).then(
2192 function(value) {
2193 timer && clearTimeout(timer);
2194 fulfill(value);
2195 },
2196 function(error) {
2197 timer && clearTimeout(timer);
2198 reject(error);
2199 });
2200 });
2201 }, opt_message || '<anonymous wait: promise resolution>');
2202 }
2203
2204 if (!goog.isFunction(condition)) {
2205 throw TypeError('Invalid condition; must be a function or promise: ' +
2206 goog.typeOf(condition));
2207 }
2208
2209 if (promise.isGenerator(condition)) {
2210 condition = goog.partial(promise.consume, condition);
2211 }
2212
2213 var self = this;
2214 return this.execute(function() {
2215 var startTime = goog.now();
2216 return new promise.Promise(function(fulfill, reject) {
2217 pollCondition();
2218
2219 function pollCondition() {
2220 var conditionFn = /** @type {function()} */(condition);
2221 self.execute(conditionFn).then(function(value) {
2222 var elapsed = goog.now() - startTime;
2223 if (!!value) {
2224 fulfill(value);
2225 } else if (timeout && elapsed >= timeout) {
2226 reject(new Error((opt_message ? opt_message + '\n' : '') +
2227 'Wait timed out after ' + elapsed + 'ms'));
2228 } else {
2229 // Do not use asyncRun here because we need a non-micro yield
2230 // here so the UI thread is given a chance when running in a
2231 // browser.
2232 setTimeout(pollCondition, 0);
2233 }
2234 }, reject);
2235 }
2236 });
2237 }, opt_message || '<anonymous wait>');
2238 },
2239
2240 /**
2241 * Executes a function in the next available turn of the JavaScript event
2242 * loop. This ensures the function runs with its own task queue and any
2243 * scheduled tasks will run in "parallel" to those scheduled in the current
2244 * function.
2245 *
2246 * flow.execute(() => console.log('a'));
2247 * flow.execute(() => console.log('b'));
2248 * flow.execute(() => console.log('c'));
2249 * flow.async(() => {
2250 * flow.execute(() => console.log('d'));
2251 * flow.execute(() => console.log('e'));
2252 * });
2253 * flow.async(() => {
2254 * flow.execute(() => console.log('f'));
2255 * flow.execute(() => console.log('g'));
2256 * });
2257 * flow.once('idle', () => console.log('fin'));
2258 * // a
2259 * // d
2260 * // f
2261 * // b
2262 * // e
2263 * // g
2264 * // c
2265 * // fin
2266 *
2267 * If the function itself throws, the error will be treated the same as an
2268 * unhandled rejection within the control flow.
2269 *
2270 * __NOTE__: This function is considered _unstable_.
2271 *
2272 * @param {!Function} fn The function to execute.
2273 * @param {Object=} opt_self The object in whose context to run the function.
2274 * @param {...*} var_args Any arguments to pass to the function.
2275 */
2276 async: function(fn, opt_self, var_args) {
2277 asyncRun(function() {
2278 // Clear any lingering queues, forces getActiveQueue_ to create a new one.
2279 this.activeQueue_ = null;
2280 var q = this.getActiveQueue_();
2281 try {
2282 q.execute_(fn.bind(opt_self, var_args));
2283 } catch (ex) {
2284 var cancellationError = promise.CancellationError.wrap(ex,
2285 'Function passed to ControlFlow.async() threw');
2286 cancellationError.silent_ = true;
2287 q.abort_(cancellationError);
2288 } finally {
2289 this.activeQueue_ = null;
2290 }
2291 }, this);
2292 },
2293
2294 /**
2295 * Event handler for when a task queue is exhausted. This starts the shutdown
2296 * sequence for this instance if there are no remaining task queues: after
2297 * one turn of the event loop, this object will emit the
2298 * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event to signal
2299 * listeners that it has completed. During this wait, if another task is
2300 * scheduled, the shutdown will be aborted.
2301 *
2302 * @param {!TaskQueue} q the completed task queue.
2303 * @private
2304 */
2305 onQueueEnd_: function(q) {
2306 if (!this.taskQueues_) {
2307 return;
2308 }
2309 this.taskQueues_.delete(q);
2310
2311 vlog(1, () => q + ' has finished');
2312 vlog(1, () => this.taskQueues_.size + ' queues remain\n' + this, this);
2313
2314 if (!this.taskQueues_.size) {
2315 asserts.assert(!this.shutdownTask_, 'Already have a shutdown task??');
2316 this.shutdownTask_ = new MicroTask(this.shutdown_, this);
2317 }
2318 },
2319
2320 /**
2321 * Event handler for when a task queue terminates with an error. This triggers
2322 * the cancellation of all other task queues and a
2323 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION} event.
2324 * If there are no error event listeners registered with this instance, the
2325 * error will be rethrown to the global error handler.
2326 *
2327 * @param {*} error the error that caused the task queue to terminate.
2328 * @param {!TaskQueue} q the task queue.
2329 * @private
2330 */
2331 onQueueError_: function(error, q) {
2332 if (this.taskQueues_) {
2333 this.taskQueues_.delete(q);
2334 }
2335 this.cancelQueues_(promise.CancellationError.wrap(
2336 error, 'There was an uncaught error in the control flow'));
2337 this.cancelShutdown_();
2338 this.cancelHold_();
2339
2340 var listeners = this.listeners(
2341 promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
2342 if (!listeners.length) {
2343 throwException(error);
2344 } else {
2345 this.reportUncaughtException_(error);
2346 }
2347 },
2348
2349 /**
2350 * Cancels all remaining task queues.
2351 * @param {!promise.CancellationError} reason The cancellation reason.
2352 * @private
2353 */
2354 cancelQueues_: function(reason) {
2355 reason.silent_ = true;
2356 if (this.taskQueues_) {
2357 for (var q of this.taskQueues_) {
2358 q.removeAllListeners();
2359 q.abort_(reason);
2360 }
2361 this.taskQueues_.clear();
2362 this.taskQueues_ = null;
2363 }
2364 },
2365
2366 /**
2367 * Reports an uncaught exception using a
2368 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION} event.
2369 *
2370 * @param {*} e the error to report.
2371 * @private
2372 */
2373 reportUncaughtException_: function(e) {
2374 this.emit(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, e);
2375 },
2376
2377 /** @private */
2378 cancelHold_: function() {
2379 if (this.hold_) {
2380 clearInterval(this.hold_);
2381 this.hold_ = null;
2382 }
2383 },
2384
2385 /** @private */
2386 shutdown_: function() {
2387 vlog(1, () => 'Going idle: ' + this);
2388 this.cancelHold_();
2389 this.shutdownTask_ = null;
2390 this.emit(promise.ControlFlow.EventType.IDLE);
2391 },
2392
2393 /**
2394 * Cancels the shutdown sequence if it is currently scheduled.
2395 * @private
2396 */
2397 cancelShutdown_: function() {
2398 if (this.shutdownTask_) {
2399 this.shutdownTask_.cancel();
2400 this.shutdownTask_ = null;
2401 }
2402 }
2403});
2404
2405
2406/**
2407 * Events that may be emitted by an {@link webdriver.promise.ControlFlow}.
2408 * @enum {string}
2409 */
2410promise.ControlFlow.EventType = {
2411
2412 /** Emitted when all tasks have been successfully executed. */
2413 IDLE: 'idle',
2414
2415 /** Emitted when a ControlFlow has been reset. */
2416 RESET: 'reset',
2417
2418 /** Emitted whenever a new task has been scheduled. */
2419 SCHEDULE_TASK: 'scheduleTask',
2420
2421 /**
2422 * Emitted whenever a control flow aborts due to an unhandled promise
2423 * rejection. This event will be emitted along with the offending rejection
2424 * reason. Upon emitting this event, the control flow will empty its task
2425 * queue and revert to its initial state.
2426 */
2427 UNCAUGHT_EXCEPTION: 'uncaughtException'
2428};
2429
2430
2431/**
2432 * Wraps a function to execute as a cancellable micro task.
2433 * @final
2434 */
2435var MicroTask = goog.defineClass(null, {
2436 /**
2437 * @param {function(this: THIS)} fn The function to run as a micro task.
2438 * @param {THIS=} opt_scope The scope to run the function in.
2439 * @template THIS
2440 */
2441 constructor: function(fn, opt_scope) {
2442 /** @private {boolean} */
2443 this.cancelled_ = false;
2444 asyncRun(function() {
2445 if (!this.cancelled_) {
2446 fn.call(opt_scope);
2447 }
2448 }, this);
2449 },
2450
2451 /**
2452 * Cancels the execution of this task. Note: this will not prevent the task
2453 * timer from firing, just the invocation of the wrapped function.
2454 */
2455 cancel: function() {
2456 this.cancelled_ = true;
2457 }
2458});
2459
2460
2461/**
2462 * A task to be executed by a {@link webdriver.promise.ControlFlow}.
2463 *
2464 * @final
2465 */
2466var Task = goog.defineClass(promise.Deferred, {
2467 /**
2468 * @param {!promise.ControlFlow} flow The flow this instances belongs
2469 * to.
2470 * @param {function(): (T|!promise.Promise<T>)} fn The function to
2471 * call when the task executes. If it returns a
2472 * {@link webdriver.promise.Promise}, the flow will wait for it to be
2473 * resolved before starting the next task.
2474 * @param {string} description A description of the task for debugging.
2475 * @param {{name: string, top: !Function}=} opt_stackOptions Options to use
2476 * when capturing the stacktrace for when this task was created.
2477 * @constructor
2478 * @extends {promise.Deferred<T>}
2479 * @template T
2480 */
2481 constructor: function(flow, fn, description, opt_stackOptions) {
2482 Task.base(this, 'constructor', flow);
2483
2484 /** @type {function(): (T|!promise.Promise<T>)} */
2485 this.execute = fn;
2486
2487 /** @type {string} */
2488 this.description = description;
2489
2490 /** @type {TaskQueue} */
2491 this.queue = null;
2492
2493 /**
2494 * Whether this task is considered block. A blocked task may be registered
2495 * in a task queue, but will be dropped if it is still blocked when it
2496 * reaches the front of the queue. A dropped task may always be rescheduled.
2497 *
2498 * Blocked tasks are used when a callback is attached to an unsettled
2499 * promise to reserve a spot in line (in a manner of speaking). If the
2500 * promise is not settled before the callback reaches the front of the
2501 * of the queue, it will be dropped. Once the promise is settled, the
2502 * dropped task will be rescheduled as an interrupt on the currently task
2503 * queue.
2504 *
2505 * @type {boolean}
2506 */
2507 this.blocked = false;
2508
2509 if (opt_stackOptions) {
2510 this.promise.stack_ = promise.captureStackTrace(
2511 opt_stackOptions.name, this.description, opt_stackOptions.top);
2512 }
2513 },
2514
2515 /** @override */
2516 toString: function() {
2517 return 'Task::' + goog.getUid(this) + '<' + this.description + '>';
2518 }
2519});
2520
2521
2522/** @enum {string} */
2523var TaskQueueState = {
2524 NEW: 'new',
2525 STARTED: 'started',
2526 FINISHED: 'finished'
2527};
2528
2529
2530/**
2531 * @final
2532 */
2533var TaskQueue = goog.defineClass(EventEmitter, {
2534 /** @param {!promise.ControlFlow} flow . */
2535 constructor: function(flow) {
2536 TaskQueue.base(this, 'constructor');
2537 goog.getUid(this);
2538
2539 /** @private {string} */
2540 this.name_ = 'TaskQueue::' + goog.getUid(this);
2541
2542 /** @private {!promise.ControlFlow} */
2543 this.flow_ = flow;
2544
2545 /** @private {!Array<!Task>} */
2546 this.tasks_ = [];
2547
2548 /** @private {Array<!Task>} */
2549 this.interrupts_ = null;
2550
2551 /** @private {({task: !Task, q: !TaskQueue}|null)} */
2552 this.pending_ = null;
2553
2554 /** @private {TaskQueueState} */
2555 this.state_ = TaskQueueState.NEW;
2556
2557 /** @private {!Set<!webdriver.promise.Promise>} */
2558 this.unhandledRejections_ = new Set();
2559 },
2560
2561 /** @override */
2562 toString: function() {
2563 return 'TaskQueue::' + goog.getUid(this);
2564 },
2565
2566 /**
2567 * @param {!webdriver.promise.Promise} promise .
2568 */
2569 addUnhandledRejection: function(promise) {
2570 // TODO: node 4.0.0+
2571 vlog(2, () => this + ' registering unhandled rejection: ' + promise, this);
2572 this.unhandledRejections_.add(promise);
2573 },
2574
2575 /**
2576 * @param {!webdriver.promise.Promise} promise .
2577 */
2578 clearUnhandledRejection: function(promise) {
2579 var deleted = this.unhandledRejections_.delete(promise);
2580 if (deleted) {
2581 // TODO: node 4.0.0+
2582 vlog(2, () => this + ' clearing unhandled rejection: ' + promise, this);
2583 }
2584 },
2585
2586 /**
2587 * Enqueues a new task for execution.
2588 * @param {!Task} task The task to enqueue.
2589 * @throws {Error} If this instance has already started execution.
2590 */
2591 enqueue: function(task) {
2592 if (this.state_ !== TaskQueueState.NEW) {
2593 throw Error('TaskQueue has started: ' + this);
2594 }
2595
2596 if (task.queue) {
2597 throw Error('Task is already scheduled in another queue');
2598 }
2599
2600 this.tasks_.push(task);
2601 task.queue = this;
2602 task.promise[CANCEL_HANDLER_SYMBOL] =
2603 this.onTaskCancelled_.bind(this, task);
2604
2605 vlog(1, () => this + '.enqueue(' + task + ')', this);
2606 vlog(2, () => this.flow_.toString(), this);
2607 },
2608
2609 /**
2610 * Schedules the callbacks registered on the given promise in this queue.
2611 *
2612 * @param {!promise.Promise} promise the promise whose callbacks should be
2613 * registered as interrupts in this task queue.
2614 * @throws {Error} if this queue has already finished.
2615 */
2616 scheduleCallbacks: function(promise) {
2617 if (this.state_ === TaskQueueState.FINISHED) {
2618 throw new Error('cannot interrupt a finished q(' + this + ')');
2619 }
2620
2621 if (this.pending_ && this.pending_.task.promise === promise) {
2622 this.pending_.task.promise.queue_ = null;
2623 this.pending_ = null;
2624 asyncRun(this.executeNext_, this);
2625 }
2626
2627 if (!promise.callbacks_) {
2628 return;
2629 }
2630 promise.callbacks_.forEach(function(cb) {
2631 cb.promise[CANCEL_HANDLER_SYMBOL] = this.onTaskCancelled_.bind(this, cb);
2632
2633 cb.blocked = false;
2634 if (cb.queue === this && this.tasks_.indexOf(cb) !== -1) {
2635 return;
2636 }
2637
2638 if (cb.queue) {
2639 cb.queue.dropTask_(cb);
2640 }
2641
2642 cb.queue = this;
2643 if (!this.interrupts_) {
2644 this.interrupts_ = [];
2645 }
2646 this.interrupts_.push(cb);
2647 }, this);
2648 promise.callbacks_ = null;
2649 vlog(2, () => this + ' interrupted\n' + this.flow_, this);
2650 },
2651
2652 /**
2653 * Starts executing tasks in this queue. Once called, no further tasks may
2654 * be {@linkplain #enqueue() enqueued} with this instance.
2655 *
2656 * @throws {Error} if this queue has already been started.
2657 */
2658 start: function() {
2659 if (this.state_ !== TaskQueueState.NEW) {
2660 throw new Error('TaskQueue has already started');
2661 }
2662 // Always asynchronously execute next, even if there doesn't look like
2663 // there is anything in the queue. This will catch pending unhandled
2664 // rejections that were registered before start was called.
2665 asyncRun(this.executeNext_, this);
2666 },
2667
2668 /**
2669 * Aborts this task queue. If there are any scheduled tasks, they are silently
2670 * cancelled and discarded (their callbacks will never fire). If this queue
2671 * has a _pending_ task, the abortion error is used to cancel that task.
2672 * Otherwise, this queue will emit an error event.
2673 *
2674 * @param {*} error The abortion reason.
2675 * @private
2676 */
2677 abort_: function(error) {
2678 var cancellation;
2679
2680 if (error instanceof FlowResetError) {
2681 cancellation = error;
2682 } else {
2683 cancellation = new DiscardedTaskError(error);
2684 }
2685
2686 if (this.interrupts_ && this.interrupts_.length) {
2687 this.interrupts_.forEach((t) => t.reject(cancellation));
2688 this.interrupts_ = [];
2689 }
2690
2691 if (this.tasks_ && this.tasks_.length) {
2692 this.tasks_.forEach((t) => t.reject(cancellation));
2693 this.tasks_ = [];
2694 }
2695
2696 if (this.pending_) {
2697 vlog(2, () => this + '.abort(); cancelling pending task', this);
2698 this.pending_.task.cancel(
2699 /** @type {!webdriver.promise.CancellationError} */(error));
2700
2701 } else {
2702 vlog(2, () => this + '.abort(); emitting error event', this);
2703 this.emit('error', error, this);
2704 }
2705 },
2706
2707 /** @private */
2708 executeNext_: function() {
2709 if (this.state_ === TaskQueueState.FINISHED) {
2710 return;
2711 }
2712 this.state_ = TaskQueueState.STARTED;
2713
2714 if (this.pending_ != null || this.processUnhandledRejections_()) {
2715 return;
2716 }
2717
2718 var task;
2719 do {
2720 task = this.getNextTask_();
2721 } while (task && !task.promise.isPending());
2722
2723 if (!task) {
2724 this.state_ = TaskQueueState.FINISHED;
2725 this.tasks_ = [];
2726 this.interrupts_ = null;
2727 vlog(2, () => this + '.emit(end)', this);
2728 this.emit('end', this);
2729 return;
2730 }
2731
2732 var self = this;
2733 var subQ = new TaskQueue(this.flow_);
2734 subQ.once('end', () => self.onTaskComplete_(result))
2735 .once('error', (e) => self.onTaskFailure_(result, e));
2736 vlog(2, () => self + ' created ' + subQ + ' for ' + task);
2737
2738 var result = undefined;
2739 try {
2740 this.pending_ = {task: task, q: subQ};
2741 task.promise.queue_ = this;
2742 result = subQ.execute_(task.execute);
2743 subQ.start();
2744 } catch (ex) {
2745 subQ.abort_(ex);
2746 }
2747 },
2748
2749
2750 /**
2751 * @param {!Function} fn .
2752 * @return {T} .
2753 * @template T
2754 * @private
2755 */
2756 execute_: function(fn) {
2757 try {
2758 activeFlows.push(this.flow_);
2759 this.flow_.activeQueue_ = this;
2760 return fn();
2761 } finally {
2762 this.flow_.activeQueue_ = null;
2763 activeFlows.pop();
2764 }
2765 },
2766
2767 /**
2768 * Process any unhandled rejections registered with this task queue. If there
2769 * is a rejection, this queue will be aborted with the rejection error. If
2770 * there are multiple rejections registered, this queue will be aborted with
2771 * a {@link promise.MultipleUnhandledRejectionError}.
2772 * @return {boolean} whether there was an unhandled rejection.
2773 * @private
2774 */
2775 processUnhandledRejections_: function() {
2776 if (!this.unhandledRejections_.size) {
2777 return false;
2778 }
2779
2780 var errors = new Set();
2781 for (var rejection of this.unhandledRejections_) {
2782 errors.add(rejection.value_);
2783 }
2784 this.unhandledRejections_.clear();
2785
2786 var errorToReport = errors.size === 1
2787 ? errors.values().next().value
2788 : new promise.MultipleUnhandledRejectionError(errors);
2789
2790 vlog(1, () => this + ' aborting due to unhandled rejections', this);
2791 if (this.flow_.propagateUnhandledRejections_) {
2792 this.abort_(errorToReport);
2793 return true;
2794 } else {
2795 vlog(1, 'error propagation disabled; reporting to control flow');
2796 this.flow_.reportUncaughtException_(errorToReport);
2797 return false;
2798 }
2799 },
2800
2801 /**
2802 * @param {!Task} task The task to drop.
2803 * @private
2804 */
2805 dropTask_: function(task) {
2806 var index;
2807 if (this.interrupts_) {
2808 index = this.interrupts_.indexOf(task);
2809 if (index != -1) {
2810 task.queue = null;
2811 this.interrupts_.splice(index, 1);
2812 return;
2813 }
2814 }
2815
2816 index = this.tasks_.indexOf(task);
2817 if (index != -1) {
2818 task.queue = null;
2819 this.tasks_.splice(index, 1);
2820 }
2821 },
2822
2823 /**
2824 * @param {!Task} task The task that was cancelled.
2825 * @param {!promise.CancellationError} reason The cancellation reason.
2826 * @private
2827 */
2828 onTaskCancelled_: function(task, reason) {
2829 if (this.pending_ && this.pending_.task === task) {
2830 this.pending_.q.abort_(reason);
2831 } else {
2832 this.dropTask_(task);
2833 }
2834 },
2835
2836 /**
2837 * @param {*} value the value originally returned by the task function.
2838 * @private
2839 */
2840 onTaskComplete_: function(value) {
2841 if (this.pending_) {
2842 this.pending_.task.fulfill(value);
2843 }
2844 },
2845
2846 /**
2847 * @param {*} taskFnResult the value originally returned by the task function.
2848 * @param {*} error the error that caused the task function to terminate.
2849 * @private
2850 */
2851 onTaskFailure_: function(taskFnResult, error) {
2852 if (promise.Thenable.isImplementation(taskFnResult)) {
2853 taskFnResult.cancel(promise.CancellationError.wrap(error));
2854 }
2855 this.pending_.task.reject(error);
2856 },
2857
2858 /**
2859 * @return {(Task|undefined)} the next task scheduled within this queue,
2860 * if any.
2861 * @private
2862 */
2863 getNextTask_: function() {
2864 var task = undefined;
2865 while (true) {
2866 if (this.interrupts_) {
2867 task = this.interrupts_.shift();
2868 }
2869 if (!task && this.tasks_) {
2870 task = this.tasks_.shift();
2871 }
2872 if (task && task.blocked) {
2873 vlog(2, () => this + ' skipping blocked task ' + task, this);
2874 task.queue = null;
2875 task = null;
2876 continue;
2877 }
2878 return task;
2879 }
2880 }
2881});
2882
2883
2884
2885/**
2886 * The default flow to use if no others are active.
2887 * @type {!promise.ControlFlow}
2888 */
2889var defaultFlow = new promise.ControlFlow();
2890
2891
2892/**
2893 * A stack of active control flows, with the top of the stack used to schedule
2894 * commands. When there are multiple flows on the stack, the flow at index N
2895 * represents a callback triggered within a task owned by the flow at index
2896 * N-1.
2897 * @type {!Array<!promise.ControlFlow>}
2898 */
2899var activeFlows = [];
2900
2901
2902/**
2903 * Changes the default flow to use when no others are active.
2904 * @param {!promise.ControlFlow} flow The new default flow.
2905 * @throws {Error} If the default flow is not currently active.
2906 */
2907promise.setDefaultFlow = function(flow) {
2908 if (activeFlows.length) {
2909 throw Error('You may only change the default flow while it is active');
2910 }
2911 defaultFlow = flow;
2912};
2913
2914
2915/**
2916 * @return {!promise.ControlFlow} The currently active control flow.
2917 */
2918promise.controlFlow = function() {
2919 return /** @type {!promise.ControlFlow} */ (
2920 Arrays.peek(activeFlows) || defaultFlow);
2921};
2922
2923
2924/**
2925 * Creates a new control flow. The provided callback will be invoked as the
2926 * first task within the new flow, with the flow as its sole argument. Returns
2927 * a promise that resolves to the callback result.
2928 * @param {function(!promise.ControlFlow)} callback The entry point
2929 * to the newly created flow.
2930 * @return {!promise.Promise} A promise that resolves to the callback
2931 * result.
2932 */
2933promise.createFlow = function(callback) {
2934 var flow = new promise.ControlFlow;
2935 return flow.execute(function() {
2936 return callback(flow);
2937 });
2938};
2939
2940
2941/**
2942 * Tests is a function is a generator.
2943 * @param {!Function} fn The function to test.
2944 * @return {boolean} Whether the function is a generator.
2945 */
2946promise.isGenerator = function(fn) {
2947 return fn.constructor.name === 'GeneratorFunction';
2948};
2949
2950
2951/**
2952 * Consumes a {@code GeneratorFunction}. Each time the generator yields a
2953 * promise, this function will wait for it to be fulfilled before feeding the
2954 * fulfilled value back into {@code next}. Likewise, if a yielded promise is
2955 * rejected, the rejection error will be passed to {@code throw}.
2956 *
2957 * __Example 1:__ the Fibonacci Sequence.
2958 *
2959 * promise.consume(function* fibonacci() {
2960 * var n1 = 1, n2 = 1;
2961 * for (var i = 0; i < 4; ++i) {
2962 * var tmp = yield n1 + n2;
2963 * n1 = n2;
2964 * n2 = tmp;
2965 * }
2966 * return n1 + n2;
2967 * }).then(function(result) {
2968 * console.log(result); // 13
2969 * });
2970 *
2971 * __Example 2:__ a generator that throws.
2972 *
2973 * promise.consume(function* () {
2974 * yield promise.delayed(250).then(function() {
2975 * throw Error('boom');
2976 * });
2977 * }).thenCatch(function(e) {
2978 * console.log(e.toString()); // Error: boom
2979 * });
2980 *
2981 * @param {!Function} generatorFn The generator function to execute.
2982 * @param {Object=} opt_self The object to use as "this" when invoking the
2983 * initial generator.
2984 * @param {...*} var_args Any arguments to pass to the initial generator.
2985 * @return {!promise.Promise<?>} A promise that will resolve to the
2986 * generator's final result.
2987 * @throws {TypeError} If the given function is not a generator.
2988 */
2989promise.consume = function(generatorFn, opt_self, var_args) {
2990 if (!promise.isGenerator(generatorFn)) {
2991 throw new TypeError('Input is not a GeneratorFunction: ' +
2992 generatorFn.constructor.name);
2993 }
2994
2995 var deferred = promise.defer();
2996 var generator = generatorFn.apply(opt_self, Arrays.slice(arguments, 2));
2997 callNext();
2998 return deferred.promise;
2999
3000 /** @param {*=} opt_value . */
3001 function callNext(opt_value) {
3002 pump(generator.next, opt_value);
3003 }
3004
3005 /** @param {*=} opt_error . */
3006 function callThrow(opt_error) {
3007 // Dictionary lookup required because Closure compiler's built-in
3008 // externs does not include GeneratorFunction.prototype.throw.
3009 pump(generator['throw'], opt_error);
3010 }
3011
3012 function pump(fn, opt_arg) {
3013 if (!deferred.isPending()) {
3014 return; // Defererd was cancelled; silently abort.
3015 }
3016
3017 try {
3018 var result = fn.call(generator, opt_arg);
3019 } catch (ex) {
3020 deferred.reject(ex);
3021 return;
3022 }
3023
3024 if (result.done) {
3025 deferred.fulfill(result.value);
3026 return;
3027 }
3028
3029 promise.asap(result.value, callNext, callThrow);
3030 }
3031};