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