UNPKG

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