lib/goog/promise/promise.js

1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15goog.provide('goog.Promise');
16
17goog.require('goog.Thenable');
18goog.require('goog.asserts');
19goog.require('goog.async.FreeList');
20goog.require('goog.async.run');
21goog.require('goog.async.throwException');
22goog.require('goog.debug.Error');
23goog.require('goog.promise.Resolver');
24
25
26
27/**
28 * Promises provide a result that may be resolved asynchronously. A Promise may
29 * be resolved by being fulfilled with a fulfillment value, rejected with a
30 * rejection reason, or blocked by another Promise. A Promise is said to be
31 * settled if it is either fulfilled or rejected. Once settled, the Promise
32 * result is immutable.
33 *
34 * Promises may represent results of any type, including undefined. Rejection
35 * reasons are typically Errors, but may also be of any type. Closure Promises
36 * allow for optional type annotations that enforce that fulfillment values are
37 * of the appropriate types at compile time.
38 *
39 * The result of a Promise is accessible by calling {@code then} and registering
40 * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise
41 * is settled, the relevant callbacks are invoked with the fulfillment value or
42 * rejection reason as argument. Callbacks are always invoked in the order they
43 * were registered, even when additional {@code then} calls are made from inside
44 * another callback. A callback is always run asynchronously sometime after the
45 * scope containing the registering {@code then} invocation has returned.
46 *
47 * If a Promise is resolved with another Promise, the first Promise will block
48 * until the second is settled, and then assumes the same result as the second
49 * Promise. This allows Promises to depend on the results of other Promises,
50 * linking together multiple asynchronous operations.
51 *
52 * This implementation is compatible with the Promises/A+ specification and
53 * passes that specification's conformance test suite. A Closure Promise may be
54 * resolved with a Promise instance (or sufficiently compatible Promise-like
55 * object) created by other Promise implementations. From the specification,
56 * Promise-like objects are known as "Thenables".
57 *
58 * @see http://promisesaplus.com/
59 *
60 * @param {function(
61 * this:RESOLVER_CONTEXT,
62 * function((TYPE|IThenable<TYPE>|Thenable)=),
63 * function(*=)): void} resolver
64 * Initialization function that is invoked immediately with {@code resolve}
65 * and {@code reject} functions as arguments. The Promise is resolved or
66 * rejected with the first argument passed to either function.
67 * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the
68 * resolver function. If unspecified, the resolver function will be executed
69 * in the default scope.
70 * @constructor
71 * @struct
72 * @final
73 * @implements {goog.Thenable<TYPE>}
74 * @template TYPE,RESOLVER_CONTEXT
75 */
76goog.Promise = function(resolver, opt_context) {
77 /**
78 * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or
79 * BLOCKED.
80 * @private {goog.Promise.State_}
81 */
82 this.state_ = goog.Promise.State_.PENDING;
83
84 /**
85 * The settled result of the Promise. Immutable once set with either a
86 * fulfillment value or rejection reason.
87 * @private {*}
88 */
89 this.result_ = undefined;
90
91 /**
92 * For Promises created by calling {@code then()}, the originating parent.
93 * @private {goog.Promise}
94 */
95 this.parent_ = null;
96
97 /**
98 * The linked list of {@code onFulfilled} and {@code onRejected} callbacks
99 * added to this Promise by calls to {@code then()}.
100 * @private {?goog.Promise.CallbackEntry_}
101 */
102 this.callbackEntries_ = null;
103
104 /**
105 * The tail of the linked list of {@code onFulfilled} and {@code onRejected}
106 * callbacks added to this Promise by calls to {@code then()}.
107 * @private {?goog.Promise.CallbackEntry_}
108 */
109 this.callbackEntriesTail_ = null;
110
111 /**
112 * Whether the Promise is in the queue of Promises to execute.
113 * @private {boolean}
114 */
115 this.executing_ = false;
116
117 if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
118 /**
119 * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater
120 * than 0 milliseconds. The ID is set when the Promise is rejected, and
121 * cleared only if an {@code onRejected} callback is invoked for the
122 * Promise (or one of its descendants) before the delay is exceeded.
123 *
124 * If the rejection is not handled before the timeout completes, the
125 * rejection reason is passed to the unhandled rejection handler.
126 * @private {number}
127 */
128 this.unhandledRejectionId_ = 0;
129 } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
130 /**
131 * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a
132 * boolean that is set if the Promise is rejected, and reset to false if an
133 * {@code onRejected} callback is invoked for the Promise (or one of its
134 * descendants). If the rejection is not handled before the next timestep,
135 * the rejection reason is passed to the unhandled rejection handler.
136 * @private {boolean}
137 */
138 this.hadUnhandledRejection_ = false;
139 }
140
141 if (goog.Promise.LONG_STACK_TRACES) {
142 /**
143 * A list of stack trace frames pointing to the locations where this Promise
144 * was created or had callbacks added to it. Saved to add additional context
145 * to stack traces when an exception is thrown.
146 * @private {!Array<string>}
147 */
148 this.stack_ = [];
149 this.addStackTrace_(new Error('created'));
150
151 /**
152 * Index of the most recently executed stack frame entry.
153 * @private {number}
154 */
155 this.currentStep_ = 0;
156 }
157
158 if (resolver == goog.Promise.RESOLVE_FAST_PATH_) {
159 // If the special sentinel resolver value is passed (from
160 // goog.Promise.resolve) we short cut to immediately resolve the promise
161 // using the value passed as opt_context. Don't try this at home.
162 this.resolve_(goog.Promise.State_.FULFILLED, opt_context);
163 } else {
164 try {
165 var self = this;
166 resolver.call(
167 opt_context,
168 function(value) {
169 self.resolve_(goog.Promise.State_.FULFILLED, value);
170 },
171 function(reason) {
172 if (goog.DEBUG &&
173 !(reason instanceof goog.Promise.CancellationError)) {
174 try {
175 // Promise was rejected. Step up one call frame to see why.
176 if (reason instanceof Error) {
177 throw reason;
178 } else {
179 throw new Error('Promise rejected.');
180 }
181 } catch (e) {
182 // Only thrown so browser dev tools can catch rejections of
183 // promises when the option to break on caught exceptions is
184 // activated.
185 }
186 }
187 self.resolve_(goog.Promise.State_.REJECTED, reason);
188 });
189 } catch (e) {
190 this.resolve_(goog.Promise.State_.REJECTED, e);
191 }
192 }
193};
194
195
196/**
197 * @define {boolean} Whether traces of {@code then} calls should be included in
198 * exceptions thrown
199 */
200goog.define('goog.Promise.LONG_STACK_TRACES', false);
201
202
203/**
204 * @define {number} The delay in milliseconds before a rejected Promise's reason
205 * is passed to the rejection handler. By default, the rejection handler
206 * rethrows the rejection reason so that it appears in the developer console or
207 * {@code window.onerror} handler.
208 *
209 * Rejections are rethrown as quickly as possible by default. A negative value
210 * disables rejection handling entirely.
211 */
212goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0);
213
214
215/**
216 * The possible internal states for a Promise. These states are not directly
217 * observable to external callers.
218 * @enum {number}
219 * @private
220 */
221goog.Promise.State_ = {
222 /** The Promise is waiting for resolution. */
223 PENDING: 0,
224
225 /** The Promise is blocked waiting for the result of another Thenable. */
226 BLOCKED: 1,
227
228 /** The Promise has been resolved with a fulfillment value. */
229 FULFILLED: 2,
230
231 /** The Promise has been resolved with a rejection reason. */
232 REJECTED: 3
233};
234
235
236
237/**
238 * Entries in the callback chain. Each call to {@code then},
239 * {@code thenCatch}, or {@code thenAlways} creates an entry containing the
240 * functions that may be invoked once the Promise is settled.
241 *
242 * @private @final @struct @constructor
243 */
244goog.Promise.CallbackEntry_ = function() {
245 /** @type {?goog.Promise} */
246 this.child = null;
247 /** @type {Function} */
248 this.onFulfilled = null;
249 /** @type {Function} */
250 this.onRejected = null;
251 /** @type {?} */
252 this.context = null;
253 /** @type {?goog.Promise.CallbackEntry_} */
254 this.next = null;
255
256 /**
257 * A boolean value to indicate this is a "thenAlways" callback entry.
258 * Unlike a normal "then/thenVoid" a "thenAlways doesn't participate
259 * in "cancel" considerations but is simply an observer and requires
260 * special handling.
261 * @type {boolean}
262 */
263 this.always = false;
264};
265
266
267/** clear the object prior to reuse */
268goog.Promise.CallbackEntry_.prototype.reset = function() {
269 this.child = null;
270 this.onFulfilled = null;
271 this.onRejected = null;
272 this.context = null;
273 this.always = false;
274};
275
276
277/**
278 * @define {number} The number of currently unused objects to keep around for
279 * reuse.
280 */
281goog.define('goog.Promise.DEFAULT_MAX_UNUSED', 100);
282
283
284/** @const @private {goog.async.FreeList<!goog.Promise.CallbackEntry_>} */
285goog.Promise.freelist_ = new goog.async.FreeList(
286 function() {
287 return new goog.Promise.CallbackEntry_();
288 },
289 function(item) {
290 item.reset();
291 },
292 goog.Promise.DEFAULT_MAX_UNUSED);
293
294
295/**
296 * @param {Function} onFulfilled
297 * @param {Function} onRejected
298 * @param {?} context
299 * @return {!goog.Promise.CallbackEntry_}
300 * @private
301 */
302goog.Promise.getCallbackEntry_ = function(onFulfilled, onRejected, context) {
303 var entry = goog.Promise.freelist_.get();
304 entry.onFulfilled = onFulfilled;
305 entry.onRejected = onRejected;
306 entry.context = context;
307 return entry;
308};
309
310
311/**
312 * @param {!goog.Promise.CallbackEntry_} entry
313 * @private
314 */
315goog.Promise.returnEntry_ = function(entry) {
316 goog.Promise.freelist_.put(entry);
317};
318
319
320/**
321 * If this passed as the first argument to the {@link goog.Promise} constructor
322 * the the opt_context is (against its primary use) used to immediately resolve
323 * the promise. This is used from {@link goog.Promise.resolve} as an
324 * optimization to avoid allocating 3 closures that are never really needed.
325 * @private @const {!Function}
326 */
327goog.Promise.RESOLVE_FAST_PATH_ = function() {};
328
329
330/**
331 * @param {(TYPE|goog.Thenable<TYPE>|Thenable)=} opt_value
332 * @return {!goog.Promise<TYPE>} A new Promise that is immediately resolved
333 * with the given value. If the input value is already a goog.Promise, it
334 * will be returned immediately without creating a new instance.
335 * @template TYPE
336 */
337goog.Promise.resolve = function(opt_value) {
338 if (opt_value instanceof goog.Promise) {
339 // Avoid creating a new object if we already have a promise object
340 // of the correct type.
341 return opt_value;
342 }
343
344 // Passes the value as the context, which is a special fast pass when
345 // goog.Promise.RESOLVE_FAST_PATH_ is passed as the first argument.
346 return new goog.Promise(goog.Promise.RESOLVE_FAST_PATH_, opt_value);
347};
348
349
350/**
351 * @param {*=} opt_reason
352 * @return {!goog.Promise} A new Promise that is immediately rejected with the
353 * given reason.
354 */
355goog.Promise.reject = function(opt_reason) {
356 return new goog.Promise(function(resolve, reject) {
357 reject(opt_reason);
358 });
359};
360
361
362/**
363 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
364 * @return {!goog.Promise<TYPE>} A Promise that receives the result of the
365 * first Promise (or Promise-like) input to settle immediately after it
366 * settles.
367 * @template TYPE
368 */
369goog.Promise.race = function(promises) {
370 return new goog.Promise(function(resolve, reject) {
371 if (!promises.length) {
372 resolve(undefined);
373 }
374 for (var i = 0, promise; promise = promises[i]; i++) {
375 goog.Promise.maybeThenVoid_(promise, resolve, reject);
376 }
377 });
378};
379
380
381/**
382 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
383 * @return {!goog.Promise<!Array<TYPE>>} A Promise that receives a list of
384 * every fulfilled value once every input Promise (or Promise-like) is
385 * successfully fulfilled, or is rejected with the first rejection reason
386 * immediately after it is rejected.
387 * @template TYPE
388 */
389goog.Promise.all = function(promises) {
390 return new goog.Promise(function(resolve, reject) {
391 var toFulfill = promises.length;
392 var values = [];
393
394 if (!toFulfill) {
395 resolve(values);
396 return;
397 }
398
399 var onFulfill = function(index, value) {
400 toFulfill--;
401 values[index] = value;
402 if (toFulfill == 0) {
403 resolve(values);
404 }
405 };
406
407 var onReject = function(reason) {
408 reject(reason);
409 };
410
411 for (var i = 0, promise; promise = promises[i]; i++) {
412 goog.Promise.maybeThenVoid_(
413 promise, goog.partial(onFulfill, i), onReject);
414 }
415 });
416};
417
418
419/**
420 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
421 * @return {!goog.Promise<!Array<{
422 * fulfilled: boolean,
423 * value: (TYPE|undefined),
424 * reason: (*|undefined)}>>} A Promise that resolves with a list of
425 * result objects once all input Promises (or Promise-like) have
426 * settled. Each result object contains a 'fulfilled' boolean indicating
427 * whether an input Promise was fulfilled or rejected. For fulfilled
428 * Promises, the resulting value is stored in the 'value' field. For
429 * rejected Promises, the rejection reason is stored in the 'reason'
430 * field.
431 * @template TYPE
432 */
433goog.Promise.allSettled = function(promises) {
434 return new goog.Promise(function(resolve, reject) {
435 var toSettle = promises.length;
436 var results = [];
437
438 if (!toSettle) {
439 resolve(results);
440 return;
441 }
442
443 var onSettled = function(index, fulfilled, result) {
444 toSettle--;
445 results[index] = fulfilled ?
446 {fulfilled: true, value: result} :
447 {fulfilled: false, reason: result};
448 if (toSettle == 0) {
449 resolve(results);
450 }
451 };
452
453 for (var i = 0, promise; promise = promises[i]; i++) {
454 goog.Promise.maybeThenVoid_(promise,
455 goog.partial(onSettled, i, true /* fulfilled */),
456 goog.partial(onSettled, i, false /* fulfilled */));
457 }
458 });
459};
460
461
462/**
463 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
464 * @return {!goog.Promise<TYPE>} A Promise that receives the value of the first
465 * input to be fulfilled, or is rejected with a list of every rejection
466 * reason if all inputs are rejected.
467 * @template TYPE
468 */
469goog.Promise.firstFulfilled = function(promises) {
470 return new goog.Promise(function(resolve, reject) {
471 var toReject = promises.length;
472 var reasons = [];
473
474 if (!toReject) {
475 resolve(undefined);
476 return;
477 }
478
479 var onFulfill = function(value) {
480 resolve(value);
481 };
482
483 var onReject = function(index, reason) {
484 toReject--;
485 reasons[index] = reason;
486 if (toReject == 0) {
487 reject(reasons);
488 }
489 };
490
491 for (var i = 0, promise; promise = promises[i]; i++) {
492 goog.Promise.maybeThenVoid_(
493 promise, onFulfill, goog.partial(onReject, i));
494 }
495 });
496};
497
498
499/**
500 * @return {!goog.promise.Resolver<TYPE>} Resolver wrapping the promise and its
501 * resolve / reject functions. Resolving or rejecting the resolver
502 * resolves or rejects the promise.
503 * @template TYPE
504 */
505goog.Promise.withResolver = function() {
506 var resolve, reject;
507 var promise = new goog.Promise(function(rs, rj) {
508 resolve = rs;
509 reject = rj;
510 });
511 return new goog.Promise.Resolver_(promise, resolve, reject);
512};
513
514
515/**
516 * Adds callbacks that will operate on the result of the Promise, returning a
517 * new child Promise.
518 *
519 * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
520 * with the fulfillment value as argument, and the child Promise will be
521 * fulfilled with the return value of the callback. If the callback throws an
522 * exception, the child Promise will be rejected with the thrown value instead.
523 *
524 * If the Promise is rejected, the {@code onRejected} callback will be invoked
525 * with the rejection reason as argument, and the child Promise will be resolved
526 * with the return value or rejected with the thrown value of the callback.
527 *
528 * @override
529 */
530goog.Promise.prototype.then = function(
531 opt_onFulfilled, opt_onRejected, opt_context) {
532
533 if (opt_onFulfilled != null) {
534 goog.asserts.assertFunction(opt_onFulfilled,
535 'opt_onFulfilled should be a function.');
536 }
537 if (opt_onRejected != null) {
538 goog.asserts.assertFunction(opt_onRejected,
539 'opt_onRejected should be a function. Did you pass opt_context ' +
540 'as the second argument instead of the third?');
541 }
542
543 if (goog.Promise.LONG_STACK_TRACES) {
544 this.addStackTrace_(new Error('then'));
545 }
546
547 return this.addChildPromise_(
548 goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null,
549 goog.isFunction(opt_onRejected) ? opt_onRejected : null,
550 opt_context);
551};
552goog.Thenable.addImplementation(goog.Promise);
553
554
555/**
556 * Adds callbacks that will operate on the result of the Promise without
557 * returning a child Promise (unlike "then").
558 *
559 * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
560 * with the fulfillment value as argument.
561 *
562 * If the Promise is rejected, the {@code onRejected} callback will be invoked
563 * with the rejection reason as argument.
564 *
565 * @param {?(function(this:THIS, TYPE):?)=} opt_onFulfilled A
566 * function that will be invoked with the fulfillment value if the Promise
567 * is fulfilled.
568 * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will
569 * be invoked with the rejection reason if the Promise is rejected.
570 * @param {THIS=} opt_context An optional context object that will be the
571 * execution context for the callbacks. By default, functions are executed
572 * with the default this.
573 * @package
574 * @template THIS
575 */
576goog.Promise.prototype.thenVoid = function(
577 opt_onFulfilled, opt_onRejected, opt_context) {
578
579 if (opt_onFulfilled != null) {
580 goog.asserts.assertFunction(opt_onFulfilled,
581 'opt_onFulfilled should be a function.');
582 }
583 if (opt_onRejected != null) {
584 goog.asserts.assertFunction(opt_onRejected,
585 'opt_onRejected should be a function. Did you pass opt_context ' +
586 'as the second argument instead of the third?');
587 }
588
589 if (goog.Promise.LONG_STACK_TRACES) {
590 this.addStackTrace_(new Error('then'));
591 }
592
593 // Note: no default rejection handler is provided here as we need to
594 // distinguish unhandled rejections.
595 this.addCallbackEntry_(goog.Promise.getCallbackEntry_(
596 opt_onFulfilled || goog.nullFunction,
597 opt_onRejected || null,
598 opt_context));
599};
600
601
602/**
603 * Calls "thenVoid" if possible to avoid allocating memory. Otherwise calls
604 * "then".
605 * @param {(goog.Thenable<TYPE>|Thenable)} promise
606 * @param {function(this:THIS, TYPE): ?} onFulfilled
607 * @param {function(this:THIS, *): *} onRejected
608 * @param {THIS=} opt_context
609 * @template THIS,TYPE
610 * @private
611 */
612goog.Promise.maybeThenVoid_ = function(
613 promise, onFulfilled, onRejected, opt_context) {
614 if (promise instanceof goog.Promise) {
615 promise.thenVoid(onFulfilled, onRejected, opt_context);
616 } else {
617 promise.then(onFulfilled, onRejected, opt_context);
618 }
619};
620
621
622/**
623 * Adds a callback that will be invoked when the Promise is settled (fulfilled
624 * or rejected). The callback receives no argument, and no new child Promise is
625 * created. This is useful for ensuring that cleanup takes place after certain
626 * asynchronous operations. Callbacks added with {@code thenAlways} will be
627 * executed in the same order with other calls to {@code then},
628 * {@code thenAlways}, or {@code thenCatch}.
629 *
630 * Since it does not produce a new child Promise, cancellation propagation is
631 * not prevented by adding callbacks with {@code thenAlways}. A Promise that has
632 * a cleanup handler added with {@code thenAlways} will be canceled if all of
633 * its children created by {@code then} (or {@code thenCatch}) are canceled.
634 * Additionally, since any rejections are not passed to the callback, it does
635 * not stop the unhandled rejection handler from running.
636 *
637 * @param {function(this:THIS): void} onSettled A function that will be invoked
638 * when the Promise is settled (fulfilled or rejected).
639 * @param {THIS=} opt_context An optional context object that will be the
640 * execution context for the callbacks. By default, functions are executed
641 * in the global scope.
642 * @return {!goog.Promise<TYPE>} This Promise, for chaining additional calls.
643 * @template THIS
644 */
645goog.Promise.prototype.thenAlways = function(onSettled, opt_context) {
646 if (goog.Promise.LONG_STACK_TRACES) {
647 this.addStackTrace_(new Error('thenAlways'));
648 }
649
650 var entry = goog.Promise.getCallbackEntry_(onSettled, onSettled, opt_context);
651 entry.always = true;
652 this.addCallbackEntry_(entry);
653 return this;
654};
655
656
657/**
658 * Adds a callback that will be invoked only if the Promise is rejected. This
659 * is equivalent to {@code then(null, onRejected)}.
660 *
661 * @param {!function(this:THIS, *): *} onRejected A function that will be
662 * invoked with the rejection reason if the Promise is rejected.
663 * @param {THIS=} opt_context An optional context object that will be the
664 * execution context for the callbacks. By default, functions are executed
665 * in the global scope.
666 * @return {!goog.Promise} A new Promise that will receive the result of the
667 * callback.
668 * @template THIS
669 */
670goog.Promise.prototype.thenCatch = function(onRejected, opt_context) {
671 if (goog.Promise.LONG_STACK_TRACES) {
672 this.addStackTrace_(new Error('thenCatch'));
673 }
674 return this.addChildPromise_(null, onRejected, opt_context);
675};
676
677
678/**
679 * Cancels the Promise if it is still pending by rejecting it with a cancel
680 * Error. No action is performed if the Promise is already resolved.
681 *
682 * All child Promises of the canceled Promise will be rejected with the same
683 * cancel error, as with normal Promise rejection. If the Promise to be canceled
684 * is the only child of a pending Promise, the parent Promise will also be
685 * canceled. Cancellation may propagate upward through multiple generations.
686 *
687 * @param {string=} opt_message An optional debugging message for describing the
688 * cancellation reason.
689 */
690goog.Promise.prototype.cancel = function(opt_message) {
691 if (this.state_ == goog.Promise.State_.PENDING) {
692 goog.async.run(function() {
693 var err = new goog.Promise.CancellationError(opt_message);
694 this.cancelInternal_(err);
695 }, this);
696 }
697};
698
699
700/**
701 * Cancels this Promise with the given error.
702 *
703 * @param {!Error} err The cancellation error.
704 * @private
705 */
706goog.Promise.prototype.cancelInternal_ = function(err) {
707 if (this.state_ == goog.Promise.State_.PENDING) {
708 if (this.parent_) {
709 // Cancel the Promise and remove it from the parent's child list.
710 this.parent_.cancelChild_(this, err);
711 this.parent_ = null;
712 } else {
713 this.resolve_(goog.Promise.State_.REJECTED, err);
714 }
715 }
716};
717
718
719/**
720 * Cancels a child Promise from the list of callback entries. If the Promise has
721 * not already been resolved, reject it with a cancel error. If there are no
722 * other children in the list of callback entries, propagate the cancellation
723 * by canceling this Promise as well.
724 *
725 * @param {!goog.Promise} childPromise The Promise to cancel.
726 * @param {!Error} err The cancel error to use for rejecting the Promise.
727 * @private
728 */
729goog.Promise.prototype.cancelChild_ = function(childPromise, err) {
730 if (!this.callbackEntries_) {
731 return;
732 }
733 var childCount = 0;
734 var childEntry = null;
735 var beforeChildEntry = null;
736
737 // Find the callback entry for the childPromise, and count whether there are
738 // additional child Promises.
739 for (var entry = this.callbackEntries_; entry; entry = entry.next) {
740 if (!entry.always) {
741 childCount++;
742 if (entry.child == childPromise) {
743 childEntry = entry;
744 }
745 if (childEntry && childCount > 1) {
746 break;
747 }
748 }
749 if (!childEntry) {
750 beforeChildEntry = entry;
751 }
752 }
753
754 // Can a child entry be missing?
755
756 // If the child Promise was the only child, cancel this Promise as well.
757 // Otherwise, reject only the child Promise with the cancel error.
758 if (childEntry) {
759 if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) {
760 this.cancelInternal_(err);
761 } else {
762 if (beforeChildEntry) {
763 this.removeEntryAfter_(beforeChildEntry);
764 } else {
765 this.popEntry_();
766 }
767
768 this.executeCallback_(
769 childEntry, goog.Promise.State_.REJECTED, err);
770 }
771 }
772};
773
774
775/**
776 * Adds a callback entry to the current Promise, and schedules callback
777 * execution if the Promise has already been settled.
778 *
779 * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing
780 * {@code onFulfilled} and {@code onRejected} callbacks to execute after
781 * the Promise is settled.
782 * @private
783 */
784goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) {
785 if (!this.hasEntry_() &&
786 (this.state_ == goog.Promise.State_.FULFILLED ||
787 this.state_ == goog.Promise.State_.REJECTED)) {
788 this.scheduleCallbacks_();
789 }
790 this.queueEntry_(callbackEntry);
791};
792
793
794/**
795 * Creates a child Promise and adds it to the callback entry list. The result of
796 * the child Promise is determined by the state of the parent Promise and the
797 * result of the {@code onFulfilled} or {@code onRejected} callbacks as
798 * specified in the Promise resolution procedure.
799 *
800 * @see http://promisesaplus.com/#the__method
801 *
802 * @param {?function(this:THIS, TYPE):
803 * (RESULT|goog.Promise<RESULT>|Thenable)} onFulfilled A callback that
804 * will be invoked if the Promise is fullfilled, or null.
805 * @param {?function(this:THIS, *): *} onRejected A callback that will be
806 * invoked if the Promise is rejected, or null.
807 * @param {THIS=} opt_context An optional execution context for the callbacks.
808 * in the default calling context.
809 * @return {!goog.Promise} The child Promise.
810 * @template RESULT,THIS
811 * @private
812 */
813goog.Promise.prototype.addChildPromise_ = function(
814 onFulfilled, onRejected, opt_context) {
815
816 /** @type {goog.Promise.CallbackEntry_} */
817 var callbackEntry = goog.Promise.getCallbackEntry_(null, null, null);
818
819 callbackEntry.child = new goog.Promise(function(resolve, reject) {
820 // Invoke onFulfilled, or resolve with the parent's value if absent.
821 callbackEntry.onFulfilled = onFulfilled ? function(value) {
822 try {
823 var result = onFulfilled.call(opt_context, value);
824 resolve(result);
825 } catch (err) {
826 reject(err);
827 }
828 } : resolve;
829
830 // Invoke onRejected, or reject with the parent's reason if absent.
831 callbackEntry.onRejected = onRejected ? function(reason) {
832 try {
833 var result = onRejected.call(opt_context, reason);
834 if (!goog.isDef(result) &&
835 reason instanceof goog.Promise.CancellationError) {
836 // Propagate cancellation to children if no other result is returned.
837 reject(reason);
838 } else {
839 resolve(result);
840 }
841 } catch (err) {
842 reject(err);
843 }
844 } : reject;
845 });
846
847 callbackEntry.child.parent_ = this;
848 this.addCallbackEntry_(callbackEntry);
849 return callbackEntry.child;
850};
851
852
853/**
854 * Unblocks the Promise and fulfills it with the given value.
855 *
856 * @param {TYPE} value
857 * @private
858 */
859goog.Promise.prototype.unblockAndFulfill_ = function(value) {
860 goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
861 this.state_ = goog.Promise.State_.PENDING;
862 this.resolve_(goog.Promise.State_.FULFILLED, value);
863};
864
865
866/**
867 * Unblocks the Promise and rejects it with the given rejection reason.
868 *
869 * @param {*} reason
870 * @private
871 */
872goog.Promise.prototype.unblockAndReject_ = function(reason) {
873 goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
874 this.state_ = goog.Promise.State_.PENDING;
875 this.resolve_(goog.Promise.State_.REJECTED, reason);
876};
877
878
879/**
880 * Attempts to resolve a Promise with a given resolution state and value. This
881 * is a no-op if the given Promise has already been resolved.
882 *
883 * If the given result is a Thenable (such as another Promise), the Promise will
884 * be settled with the same state and result as the Thenable once it is itself
885 * settled.
886 *
887 * If the given result is not a Thenable, the Promise will be settled (fulfilled
888 * or rejected) with that result based on the given state.
889 *
890 * @see http://promisesaplus.com/#the_promise_resolution_procedure
891 *
892 * @param {goog.Promise.State_} state
893 * @param {*} x The result to apply to the Promise.
894 * @private
895 */
896goog.Promise.prototype.resolve_ = function(state, x) {
897 if (this.state_ != goog.Promise.State_.PENDING) {
898 return;
899 }
900
901 if (this == x) {
902 state = goog.Promise.State_.REJECTED;
903 x = new TypeError('Promise cannot resolve to itself');
904
905 } else if (goog.Thenable.isImplementedBy(x)) {
906 x = /** @type {!goog.Thenable} */ (x);
907 this.state_ = goog.Promise.State_.BLOCKED;
908 goog.Promise.maybeThenVoid_(
909 x, this.unblockAndFulfill_, this.unblockAndReject_, this);
910 return;
911 } else if (goog.isObject(x)) {
912 try {
913 var then = x['then'];
914 if (goog.isFunction(then)) {
915 this.tryThen_(x, then);
916 return;
917 }
918 } catch (e) {
919 state = goog.Promise.State_.REJECTED;
920 x = e;
921 }
922 }
923
924 this.result_ = x;
925 this.state_ = state;
926 // Since we can no longer be cancelled, remove link to parent, so that the
927 // child promise does not keep the parent promise alive.
928 this.parent_ = null;
929 this.scheduleCallbacks_();
930
931 if (state == goog.Promise.State_.REJECTED &&
932 !(x instanceof goog.Promise.CancellationError)) {
933 goog.Promise.addUnhandledRejection_(this, x);
934 }
935};
936
937
938/**
939 * Attempts to call the {@code then} method on an object in the hopes that it is
940 * a Promise-compatible instance. This allows interoperation between different
941 * Promise implementations, however a non-compliant object may cause a Promise
942 * to hang indefinitely. If the {@code then} method throws an exception, the
943 * dependent Promise will be rejected with the thrown value.
944 *
945 * @see http://promisesaplus.com/#point-70
946 *
947 * @param {Thenable} thenable An object with a {@code then} method that may be
948 * compatible with the Promise/A+ specification.
949 * @param {!Function} then The {@code then} method of the Thenable object.
950 * @private
951 */
952goog.Promise.prototype.tryThen_ = function(thenable, then) {
953 this.state_ = goog.Promise.State_.BLOCKED;
954 var promise = this;
955 var called = false;
956
957 var resolve = function(value) {
958 if (!called) {
959 called = true;
960 promise.unblockAndFulfill_(value);
961 }
962 };
963
964 var reject = function(reason) {
965 if (!called) {
966 called = true;
967 promise.unblockAndReject_(reason);
968 }
969 };
970
971 try {
972 then.call(thenable, resolve, reject);
973 } catch (e) {
974 reject(e);
975 }
976};
977
978
979/**
980 * Executes the pending callbacks of a settled Promise after a timeout.
981 *
982 * Section 2.2.4 of the Promises/A+ specification requires that Promise
983 * callbacks must only be invoked from a call stack that only contains Promise
984 * implementation code, which we accomplish by invoking callback execution after
985 * a timeout. If {@code startExecution_} is called multiple times for the same
986 * Promise, the callback chain will be evaluated only once. Additional callbacks
987 * may be added during the evaluation phase, and will be executed in the same
988 * event loop.
989 *
990 * All Promises added to the waiting list during the same browser event loop
991 * will be executed in one batch to avoid using a separate timeout per Promise.
992 *
993 * @private
994 */
995goog.Promise.prototype.scheduleCallbacks_ = function() {
996 if (!this.executing_) {
997 this.executing_ = true;
998 goog.async.run(this.executeCallbacks_, this);
999 }
1000};
1001
1002
1003/**
1004 * @return {boolean} Whether there are any pending callbacks queued.
1005 * @private
1006 */
1007goog.Promise.prototype.hasEntry_ = function() {
1008 return !!this.callbackEntries_;
1009};
1010
1011
1012/**
1013 * @param {goog.Promise.CallbackEntry_} entry
1014 * @private
1015 */
1016goog.Promise.prototype.queueEntry_ = function(entry) {
1017 goog.asserts.assert(entry.onFulfilled != null);
1018
1019 if (this.callbackEntriesTail_) {
1020 this.callbackEntriesTail_.next = entry;
1021 this.callbackEntriesTail_ = entry;
1022 } else {
1023 // It the work queue was empty set the head too.
1024 this.callbackEntries_ = entry;
1025 this.callbackEntriesTail_ = entry;
1026 }
1027};
1028
1029
1030/**
1031 * @return {goog.Promise.CallbackEntry_} entry
1032 * @private
1033 */
1034goog.Promise.prototype.popEntry_ = function() {
1035 var entry = null;
1036 if (this.callbackEntries_) {
1037 entry = this.callbackEntries_;
1038 this.callbackEntries_ = entry.next;
1039 entry.next = null;
1040 }
1041 // It the work queue is empty clear the tail too.
1042 if (!this.callbackEntries_) {
1043 this.callbackEntriesTail_ = null;
1044 }
1045
1046 if (entry != null) {
1047 goog.asserts.assert(entry.onFulfilled != null);
1048 }
1049 return entry;
1050};
1051
1052
1053/**
1054 * @param {goog.Promise.CallbackEntry_} previous
1055 * @private
1056 */
1057goog.Promise.prototype.removeEntryAfter_ = function(previous) {
1058 goog.asserts.assert(this.callbackEntries_);
1059 goog.asserts.assert(previous != null);
1060 // If the last entry is being removed, update the tail
1061 if (previous.next == this.callbackEntriesTail_) {
1062 this.callbackEntriesTail_ = previous;
1063 }
1064
1065 previous.next = previous.next.next;
1066};
1067
1068
1069/**
1070 * Executes all pending callbacks for this Promise.
1071 *
1072 * @private
1073 */
1074goog.Promise.prototype.executeCallbacks_ = function() {
1075 var entry = null;
1076 while (entry = this.popEntry_()) {
1077 if (goog.Promise.LONG_STACK_TRACES) {
1078 this.currentStep_++;
1079 }
1080 this.executeCallback_(entry, this.state_, this.result_);
1081 }
1082 this.executing_ = false;
1083};
1084
1085
1086/**
1087 * Executes a pending callback for this Promise. Invokes an {@code onFulfilled}
1088 * or {@code onRejected} callback based on the settled state of the Promise.
1089 *
1090 * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the
1091 * onFulfilled and/or onRejected callbacks for this step.
1092 * @param {goog.Promise.State_} state The resolution status of the Promise,
1093 * either FULFILLED or REJECTED.
1094 * @param {*} result The settled result of the Promise.
1095 * @private
1096 */
1097goog.Promise.prototype.executeCallback_ = function(
1098 callbackEntry, state, result) {
1099 // Cancel an unhandled rejection if the then/thenVoid call had an onRejected.
1100 if (state == goog.Promise.State_.REJECTED &&
1101 callbackEntry.onRejected && !callbackEntry.always) {
1102 this.removeUnhandledRejection_();
1103 }
1104
1105 if (callbackEntry.child) {
1106 // When the parent is settled, the child no longer needs to hold on to it,
1107 // as the parent can no longer be canceled.
1108 callbackEntry.child.parent_ = null;
1109 goog.Promise.invokeCallback_(callbackEntry, state, result);
1110 } else {
1111 // Callbacks created with thenAlways or thenVoid do not have the rejection
1112 // handling code normally set up in the child Promise.
1113 try {
1114 callbackEntry.always ?
1115 callbackEntry.onFulfilled.call(callbackEntry.context) :
1116 goog.Promise.invokeCallback_(callbackEntry, state, result);
1117 } catch (err) {
1118 goog.Promise.handleRejection_.call(null, err);
1119 }
1120 }
1121 goog.Promise.returnEntry_(callbackEntry);
1122};
1123
1124
1125/**
1126 * Executes the onFulfilled or onRejected callback for a callbackEntry.
1127 *
1128 * @param {!goog.Promise.CallbackEntry_} callbackEntry
1129 * @param {goog.Promise.State_} state
1130 * @param {*} result
1131 * @private
1132 */
1133goog.Promise.invokeCallback_ = function(callbackEntry, state, result) {
1134 if (state == goog.Promise.State_.FULFILLED) {
1135 callbackEntry.onFulfilled.call(callbackEntry.context, result);
1136 } else if (callbackEntry.onRejected) {
1137 callbackEntry.onRejected.call(callbackEntry.context, result);
1138 }
1139};
1140
1141
1142/**
1143 * Records a stack trace entry for functions that call {@code then} or the
1144 * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}.
1145 *
1146 * @param {!Error} err An Error object created by the calling function for
1147 * providing a stack trace.
1148 * @private
1149 */
1150goog.Promise.prototype.addStackTrace_ = function(err) {
1151 if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) {
1152 // Extract the third line of the stack trace, which is the entry for the
1153 // user function that called into Promise code.
1154 var trace = err.stack.split('\n', 4)[3];
1155 var message = err.message;
1156
1157 // Pad the message to align the traces.
1158 message += Array(11 - message.length).join(' ');
1159 this.stack_.push(message + trace);
1160 }
1161};
1162
1163
1164/**
1165 * Adds extra stack trace information to an exception for the list of
1166 * asynchronous {@code then} calls that have been run for this Promise. Stack
1167 * trace information is recorded in {@see #addStackTrace_}, and appended to
1168 * rethrown errors when {@code LONG_STACK_TRACES} is enabled.
1169 *
1170 * @param {*} err An unhandled exception captured during callback execution.
1171 * @private
1172 */
1173goog.Promise.prototype.appendLongStack_ = function(err) {
1174 if (goog.Promise.LONG_STACK_TRACES &&
1175 err && goog.isString(err.stack) && this.stack_.length) {
1176 var longTrace = ['Promise trace:'];
1177
1178 for (var promise = this; promise; promise = promise.parent_) {
1179 for (var i = this.currentStep_; i >= 0; i--) {
1180 longTrace.push(promise.stack_[i]);
1181 }
1182 longTrace.push('Value: ' +
1183 '[' + (promise.state_ == goog.Promise.State_.REJECTED ?
1184 'REJECTED' : 'FULFILLED') + '] ' +
1185 '<' + String(promise.result_) + '>');
1186 }
1187 err.stack += '\n\n' + longTrace.join('\n');
1188 }
1189};
1190
1191
1192/**
1193 * Marks this rejected Promise as having being handled. Also marks any parent
1194 * Promises in the rejected state as handled. The rejection handler will no
1195 * longer be invoked for this Promise (if it has not been called already).
1196 *
1197 * @private
1198 */
1199goog.Promise.prototype.removeUnhandledRejection_ = function() {
1200 if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
1201 for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) {
1202 goog.global.clearTimeout(p.unhandledRejectionId_);
1203 p.unhandledRejectionId_ = 0;
1204 }
1205 } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
1206 for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) {
1207 p.hadUnhandledRejection_ = false;
1208 }
1209 }
1210};
1211
1212
1213/**
1214 * Marks this rejected Promise as unhandled. If no {@code onRejected} callback
1215 * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY}
1216 * expires, the reason will be passed to the unhandled rejection handler. The
1217 * handler typically rethrows the rejection reason so that it becomes visible in
1218 * the developer console.
1219 *
1220 * @param {!goog.Promise} promise The rejected Promise.
1221 * @param {*} reason The Promise rejection reason.
1222 * @private
1223 */
1224goog.Promise.addUnhandledRejection_ = function(promise, reason) {
1225 if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
1226 promise.unhandledRejectionId_ = goog.global.setTimeout(function() {
1227 promise.appendLongStack_(reason);
1228 goog.Promise.handleRejection_.call(null, reason);
1229 }, goog.Promise.UNHANDLED_REJECTION_DELAY);
1230
1231 } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
1232 promise.hadUnhandledRejection_ = true;
1233 goog.async.run(function() {
1234 if (promise.hadUnhandledRejection_) {
1235 promise.appendLongStack_(reason);
1236 goog.Promise.handleRejection_.call(null, reason);
1237 }
1238 });
1239 }
1240};
1241
1242
1243/**
1244 * A method that is invoked with the rejection reasons for Promises that are
1245 * rejected but have no {@code onRejected} callbacks registered yet.
1246 * @type {function(*)}
1247 * @private
1248 */
1249goog.Promise.handleRejection_ = goog.async.throwException;
1250
1251
1252/**
1253 * Sets a handler that will be called with reasons from unhandled rejected
1254 * Promises. If the rejected Promise (or one of its descendants) has an
1255 * {@code onRejected} callback registered, the rejection will be considered
1256 * handled, and the rejection handler will not be called.
1257 *
1258 * By default, unhandled rejections are rethrown so that the error may be
1259 * captured by the developer console or a {@code window.onerror} handler.
1260 *
1261 * @param {function(*)} handler A function that will be called with reasons from
1262 * rejected Promises. Defaults to {@code goog.async.throwException}.
1263 */
1264goog.Promise.setUnhandledRejectionHandler = function(handler) {
1265 goog.Promise.handleRejection_ = handler;
1266};
1267
1268
1269
1270/**
1271 * Error used as a rejection reason for canceled Promises.
1272 *
1273 * @param {string=} opt_message
1274 * @constructor
1275 * @extends {goog.debug.Error}
1276 * @final
1277 */
1278goog.Promise.CancellationError = function(opt_message) {
1279 goog.Promise.CancellationError.base(this, 'constructor', opt_message);
1280};
1281goog.inherits(goog.Promise.CancellationError, goog.debug.Error);
1282
1283
1284/** @override */
1285goog.Promise.CancellationError.prototype.name = 'cancel';
1286
1287
1288
1289/**
1290 * Internal implementation of the resolver interface.
1291 *
1292 * @param {!goog.Promise<TYPE>} promise
1293 * @param {function((TYPE|goog.Promise<TYPE>|Thenable)=)} resolve
1294 * @param {function(*=): void} reject
1295 * @implements {goog.promise.Resolver<TYPE>}
1296 * @final @struct
1297 * @constructor
1298 * @private
1299 * @template TYPE
1300 */
1301goog.Promise.Resolver_ = function(promise, resolve, reject) {
1302 /** @const */
1303 this.promise = promise;
1304
1305 /** @const */
1306 this.resolve = resolve;
1307
1308 /** @const */
1309 this.reject = reject;
1310};