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