Index: third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js |
diff --git a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js |
index 1c9ef5cf5fcd6fc166882b2ef1883e8e8c556734..a3580f0842487c4f3b079b679e55a0e0c0d31a7f 100644 |
--- a/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js |
+++ b/third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js |
@@ -16,6 +16,7 @@ goog.provide('goog.Promise'); |
goog.require('goog.Thenable'); |
goog.require('goog.asserts'); |
+goog.require('goog.async.FreeList'); |
goog.require('goog.async.run'); |
goog.require('goog.async.throwException'); |
goog.require('goog.debug.Error'); |
@@ -25,9 +26,10 @@ goog.require('goog.promise.Resolver'); |
/** |
* Promises provide a result that may be resolved asynchronously. A Promise may |
- * be resolved by being fulfilled or rejected with a value, which will be known |
- * as the fulfillment value or the rejection reason. Whether fulfilled or |
- * rejected, the Promise result is immutable once it is set. |
+ * be resolved by being fulfilled with a fulfillment value, rejected with a |
+ * rejection reason, or blocked by another Promise. A Promise is said to be |
+ * settled if it is either fulfilled or rejected. Once settled, the Promise |
+ * result is immutable. |
* |
* Promises may represent results of any type, including undefined. Rejection |
* reasons are typically Errors, but may also be of any type. Closure Promises |
@@ -36,14 +38,14 @@ goog.require('goog.promise.Resolver'); |
* |
* The result of a Promise is accessible by calling {@code then} and registering |
* {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise |
- * resolves, the relevant callbacks are invoked with the fulfillment value or |
+ * is settled, the relevant callbacks are invoked with the fulfillment value or |
* rejection reason as argument. Callbacks are always invoked in the order they |
* were registered, even when additional {@code then} calls are made from inside |
* another callback. A callback is always run asynchronously sometime after the |
* scope containing the registering {@code then} invocation has returned. |
* |
* If a Promise is resolved with another Promise, the first Promise will block |
- * until the second is resolved, and then assumes the same result as the second |
+ * until the second is settled, and then assumes the same result as the second |
* Promise. This allows Promises to depend on the results of other Promises, |
* linking together multiple asynchronous operations. |
* |
@@ -57,8 +59,8 @@ goog.require('goog.promise.Resolver'); |
* |
* @param {function( |
* this:RESOLVER_CONTEXT, |
- * function((TYPE|IThenable.<TYPE>|Thenable)=), |
- * function(*)): void} resolver |
+ * function((TYPE|IThenable<TYPE>|Thenable)=), |
+ * function(*=)): void} resolver |
* Initialization function that is invoked immediately with {@code resolve} |
* and {@code reject} functions as arguments. The Promise is resolved or |
* rejected with the first argument passed to either function. |
@@ -68,7 +70,7 @@ goog.require('goog.promise.Resolver'); |
* @constructor |
* @struct |
* @final |
- * @implements {goog.Thenable.<TYPE>} |
+ * @implements {goog.Thenable<TYPE>} |
* @template TYPE,RESOLVER_CONTEXT |
*/ |
goog.Promise = function(resolver, opt_context) { |
@@ -80,7 +82,7 @@ goog.Promise = function(resolver, opt_context) { |
this.state_ = goog.Promise.State_.PENDING; |
/** |
- * The resolved result of the Promise. Immutable once set with either a |
+ * The settled result of the Promise. Immutable once set with either a |
* fulfillment value or rejection reason. |
* @private {*} |
*/ |
@@ -93,13 +95,20 @@ goog.Promise = function(resolver, opt_context) { |
this.parent_ = null; |
/** |
- * The list of {@code onFulfilled} and {@code onRejected} callbacks added to |
- * this Promise by calls to {@code then()}. |
- * @private {Array.<goog.Promise.CallbackEntry_>} |
+ * The linked list of {@code onFulfilled} and {@code onRejected} callbacks |
+ * added to this Promise by calls to {@code then()}. |
+ * @private {?goog.Promise.CallbackEntry_} |
*/ |
this.callbackEntries_ = null; |
/** |
+ * The tail of the linked list of {@code onFulfilled} and {@code onRejected} |
+ * callbacks added to this Promise by calls to {@code then()}. |
+ * @private {?goog.Promise.CallbackEntry_} |
+ */ |
+ this.callbackEntriesTail_ = null; |
+ |
+ /** |
* Whether the Promise is in the queue of Promises to execute. |
* @private {boolean} |
*/ |
@@ -134,7 +143,7 @@ goog.Promise = function(resolver, opt_context) { |
* A list of stack trace frames pointing to the locations where this Promise |
* was created or had callbacks added to it. Saved to add additional context |
* to stack traces when an exception is thrown. |
- * @private {!Array.<string>} |
+ * @private {!Array<string>} |
*/ |
this.stack_ = []; |
this.addStackTrace_(new Error('created')); |
@@ -146,18 +155,38 @@ goog.Promise = function(resolver, opt_context) { |
this.currentStep_ = 0; |
} |
- try { |
- var self = this; |
- resolver.call( |
- opt_context, |
- function(value) { |
- self.resolve_(goog.Promise.State_.FULFILLED, value); |
- }, |
- function(reason) { |
- self.resolve_(goog.Promise.State_.REJECTED, reason); |
- }); |
- } catch (e) { |
- this.resolve_(goog.Promise.State_.REJECTED, e); |
+ // As an optimization, we can skip this if resolver is goog.nullFunction. |
+ // This value is passed internally when creating a promise which will be |
+ // resolved through a more optimized path. |
+ if (resolver != goog.nullFunction) { |
+ try { |
+ var self = this; |
+ resolver.call( |
+ opt_context, |
+ function(value) { |
+ self.resolve_(goog.Promise.State_.FULFILLED, value); |
+ }, |
+ function(reason) { |
+ if (goog.DEBUG && |
+ !(reason instanceof goog.Promise.CancellationError)) { |
+ try { |
+ // Promise was rejected. Step up one call frame to see why. |
+ if (reason instanceof Error) { |
+ throw reason; |
+ } else { |
+ throw new Error('Promise rejected.'); |
+ } |
+ } catch (e) { |
+ // Only thrown so browser dev tools can catch rejections of |
+ // promises when the option to break on caught exceptions is |
+ // activated. |
+ } |
+ } |
+ self.resolve_(goog.Promise.State_.REJECTED, reason); |
+ }); |
+ } catch (e) { |
+ this.resolve_(goog.Promise.State_.REJECTED, e); |
+ } |
} |
}; |
@@ -202,31 +231,109 @@ goog.Promise.State_ = { |
}; |
+ |
/** |
- * Typedef for entries in the callback chain. Each call to {@code then}, |
+ * Entries in the callback chain. Each call to {@code then}, |
* {@code thenCatch}, or {@code thenAlways} creates an entry containing the |
- * functions that may be invoked once the Promise is resolved. |
+ * functions that may be invoked once the Promise is settled. |
* |
- * @typedef {{ |
- * child: goog.Promise, |
- * onFulfilled: function(*), |
- * onRejected: function(*) |
- * }} |
+ * @private @final @struct @constructor |
+ */ |
+goog.Promise.CallbackEntry_ = function() { |
+ /** @type {?goog.Promise} */ |
+ this.child = null; |
+ /** @type {Function} */ |
+ this.onFulfilled = null; |
+ /** @type {Function} */ |
+ this.onRejected = null; |
+ /** @type {?} */ |
+ this.context = null; |
+ /** @type {?goog.Promise.CallbackEntry_} */ |
+ this.next = null; |
+ |
+ /** |
+ * A boolean value to indicate this is a "thenAlways" callback entry. |
+ * Unlike a normal "then/thenVoid" a "thenAlways doesn't participate |
+ * in "cancel" considerations but is simply an observer and requires |
+ * special handling. |
+ * @type {boolean} |
+ */ |
+ this.always = false; |
+}; |
+ |
+ |
+/** clear the object prior to reuse */ |
+goog.Promise.CallbackEntry_.prototype.reset = function() { |
+ this.child = null; |
+ this.onFulfilled = null; |
+ this.onRejected = null; |
+ this.context = null; |
+ this.always = false; |
+}; |
+ |
+ |
+/** |
+ * @define {number} The number of currently unused objects to keep around for |
+ * reuse. |
+ */ |
+goog.define('goog.Promise.DEFAULT_MAX_UNUSED', 100); |
+ |
+ |
+/** @const @private {goog.async.FreeList<!goog.Promise.CallbackEntry_>} */ |
+goog.Promise.freelist_ = new goog.async.FreeList( |
+ function() { |
+ return new goog.Promise.CallbackEntry_(); |
+ }, |
+ function(item) { |
+ item.reset(); |
+ }, |
+ goog.Promise.DEFAULT_MAX_UNUSED); |
+ |
+ |
+/** |
+ * @param {Function} onFulfilled |
+ * @param {Function} onRejected |
+ * @param {?} context |
+ * @return {!goog.Promise.CallbackEntry_} |
+ * @private |
+ */ |
+goog.Promise.getCallbackEntry_ = function(onFulfilled, onRejected, context) { |
+ var entry = goog.Promise.freelist_.get(); |
+ entry.onFulfilled = onFulfilled; |
+ entry.onRejected = onRejected; |
+ entry.context = context; |
+ return entry; |
+}; |
+ |
+ |
+/** |
+ * @param {!goog.Promise.CallbackEntry_} entry |
* @private |
*/ |
-goog.Promise.CallbackEntry_; |
+goog.Promise.returnEntry_ = function(entry) { |
+ goog.Promise.freelist_.put(entry); |
+}; |
/** |
- * @param {(TYPE|goog.Thenable.<TYPE>|Thenable)=} opt_value |
- * @return {!goog.Promise.<TYPE>} A new Promise that is immediately resolved |
- * with the given value. |
+ * @param {(TYPE|goog.Thenable<TYPE>|Thenable)=} opt_value |
+ * @return {!goog.Promise<TYPE>} A new Promise that is immediately resolved |
+ * with the given value. If the input value is already a goog.Promise, it |
+ * will be returned immediately without creating a new instance. |
* @template TYPE |
*/ |
goog.Promise.resolve = function(opt_value) { |
- return new goog.Promise(function(resolve, reject) { |
- resolve(opt_value); |
- }); |
+ if (opt_value instanceof goog.Promise) { |
+ // Avoid creating a new object if we already have a promise object |
+ // of the correct type. |
+ return opt_value; |
+ } |
+ |
+ // Passing goog.nullFunction will cause the constructor to take an optimized |
+ // path that skips calling the resolver function. |
+ var promise = new goog.Promise(goog.nullFunction); |
+ promise.resolve_(goog.Promise.State_.FULFILLED, opt_value); |
+ return promise; |
}; |
@@ -243,9 +350,32 @@ goog.Promise.reject = function(opt_reason) { |
/** |
- * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises |
- * @return {!goog.Promise.<TYPE>} A Promise that receives the result of the |
- * first Promise (or Promise-like) input to complete. |
+ * This is identical to |
+ * {@code goog.Promise.resolve(value).then(onFulfilled, onRejected)}, but it |
+ * avoids creating an unnecessary wrapper Promise when {@code value} is already |
+ * thenable. |
+ * |
+ * @param {?(goog.Thenable<TYPE>|Thenable|TYPE)} value |
+ * @param {function(TYPE): ?} onFulfilled |
+ * @param {function(*): *} onRejected |
+ * @template TYPE |
+ * @private |
+ */ |
+goog.Promise.resolveThen_ = function(value, onFulfilled, onRejected) { |
+ var isThenable = goog.Promise.maybeThen_( |
+ value, onFulfilled, onRejected, null); |
+ if (!isThenable) { |
+ goog.async.run(goog.partial(onFulfilled, value)); |
+ } |
+}; |
+ |
+ |
+/** |
+ * @param {!Array<?(goog.Promise<TYPE>|goog.Thenable<TYPE>|Thenable|*)>} |
+ * promises |
+ * @return {!goog.Promise<TYPE>} A Promise that receives the result of the |
+ * first Promise (or Promise-like) input to settle immediately after it |
+ * settles. |
* @template TYPE |
*/ |
goog.Promise.race = function(promises) { |
@@ -253,18 +383,21 @@ goog.Promise.race = function(promises) { |
if (!promises.length) { |
resolve(undefined); |
} |
- for (var i = 0, promise; promise = promises[i]; i++) { |
- promise.then(resolve, reject); |
+ for (var i = 0, promise; i < promises.length; i++) { |
+ promise = promises[i]; |
+ goog.Promise.resolveThen_(promise, resolve, reject); |
} |
}); |
}; |
/** |
- * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises |
- * @return {!goog.Promise.<!Array.<TYPE>>} A Promise that receives a list of |
+ * @param {!Array<?(goog.Promise<TYPE>|goog.Thenable<TYPE>|Thenable|*)>} |
+ * promises |
+ * @return {!goog.Promise<!Array<TYPE>>} A Promise that receives a list of |
* every fulfilled value once every input Promise (or Promise-like) is |
- * successfully fulfilled, or is rejected by the first rejection result. |
+ * successfully fulfilled, or is rejected with the first rejection reason |
+ * immediately after it is rejected. |
* @template TYPE |
*/ |
goog.Promise.all = function(promises) { |
@@ -289,16 +422,64 @@ goog.Promise.all = function(promises) { |
reject(reason); |
}; |
- for (var i = 0, promise; promise = promises[i]; i++) { |
- promise.then(goog.partial(onFulfill, i), onReject); |
+ for (var i = 0, promise; i < promises.length; i++) { |
+ promise = promises[i]; |
+ goog.Promise.resolveThen_( |
+ promise, goog.partial(onFulfill, i), onReject); |
+ } |
+ }); |
+}; |
+ |
+ |
+/** |
+ * @param {!Array<?(goog.Promise<TYPE>|goog.Thenable<TYPE>|Thenable|*)>} |
+ * promises |
+ * @return {!goog.Promise<!Array<{ |
+ * fulfilled: boolean, |
+ * value: (TYPE|undefined), |
+ * reason: (*|undefined)}>>} A Promise that resolves with a list of |
+ * result objects once all input Promises (or Promise-like) have |
+ * settled. Each result object contains a 'fulfilled' boolean indicating |
+ * whether an input Promise was fulfilled or rejected. For fulfilled |
+ * Promises, the resulting value is stored in the 'value' field. For |
+ * rejected Promises, the rejection reason is stored in the 'reason' |
+ * field. |
+ * @template TYPE |
+ */ |
+goog.Promise.allSettled = function(promises) { |
+ return new goog.Promise(function(resolve, reject) { |
+ var toSettle = promises.length; |
+ var results = []; |
+ |
+ if (!toSettle) { |
+ resolve(results); |
+ return; |
+ } |
+ |
+ var onSettled = function(index, fulfilled, result) { |
+ toSettle--; |
+ results[index] = fulfilled ? |
+ {fulfilled: true, value: result} : |
+ {fulfilled: false, reason: result}; |
+ if (toSettle == 0) { |
+ resolve(results); |
+ } |
+ }; |
+ |
+ for (var i = 0, promise; i < promises.length; i++) { |
+ promise = promises[i]; |
+ goog.Promise.resolveThen_(promise, |
+ goog.partial(onSettled, i, true /* fulfilled */), |
+ goog.partial(onSettled, i, false /* fulfilled */)); |
} |
}); |
}; |
/** |
- * @param {!Array.<!(goog.Thenable.<TYPE>|Thenable)>} promises |
- * @return {!goog.Promise.<TYPE>} A Promise that receives the value of the first |
+ * @param {!Array<?(goog.Promise<TYPE>|goog.Thenable<TYPE>|Thenable|*)>} |
+ * promises |
+ * @return {!goog.Promise<TYPE>} A Promise that receives the value of the first |
* input to be fulfilled, or is rejected with a list of every rejection |
* reason if all inputs are rejected. |
* @template TYPE |
@@ -325,15 +506,17 @@ goog.Promise.firstFulfilled = function(promises) { |
} |
}; |
- for (var i = 0, promise; promise = promises[i]; i++) { |
- promise.then(onFulfill, goog.partial(onReject, i)); |
+ for (var i = 0, promise; i < promises.length; i++) { |
+ promise = promises[i]; |
+ goog.Promise.resolveThen_( |
+ promise, onFulfill, goog.partial(onReject, i)); |
} |
}); |
}; |
/** |
- * @return {!goog.promise.Resolver.<TYPE>} Resolver wrapping the promise and its |
+ * @return {!goog.promise.Resolver<TYPE>} Resolver wrapping the promise and its |
* resolve / reject functions. Resolving or rejecting the resolver |
* resolves or rejects the promise. |
* @template TYPE |
@@ -389,8 +572,55 @@ goog.Thenable.addImplementation(goog.Promise); |
/** |
- * Adds a callback that will be invoked whether the Promise is fulfilled or |
- * rejected. The callback receives no argument, and no new child Promise is |
+ * Adds callbacks that will operate on the result of the Promise without |
+ * returning a child Promise (unlike "then"). |
+ * |
+ * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked |
+ * with the fulfillment value as argument. |
+ * |
+ * If the Promise is rejected, the {@code onRejected} callback will be invoked |
+ * with the rejection reason as argument. |
+ * |
+ * @param {?(function(this:THIS, TYPE):?)=} opt_onFulfilled A |
+ * function that will be invoked with the fulfillment value if the Promise |
+ * is fulfilled. |
+ * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will |
+ * be invoked with the rejection reason if the Promise is rejected. |
+ * @param {THIS=} opt_context An optional context object that will be the |
+ * execution context for the callbacks. By default, functions are executed |
+ * with the default this. |
+ * @package |
+ * @template THIS |
+ */ |
+goog.Promise.prototype.thenVoid = function( |
+ opt_onFulfilled, opt_onRejected, opt_context) { |
+ |
+ if (opt_onFulfilled != null) { |
+ goog.asserts.assertFunction(opt_onFulfilled, |
+ 'opt_onFulfilled should be a function.'); |
+ } |
+ if (opt_onRejected != null) { |
+ goog.asserts.assertFunction(opt_onRejected, |
+ 'opt_onRejected should be a function. Did you pass opt_context ' + |
+ 'as the second argument instead of the third?'); |
+ } |
+ |
+ if (goog.Promise.LONG_STACK_TRACES) { |
+ this.addStackTrace_(new Error('then')); |
+ } |
+ |
+ // Note: no default rejection handler is provided here as we need to |
+ // distinguish unhandled rejections. |
+ this.addCallbackEntry_(goog.Promise.getCallbackEntry_( |
+ opt_onFulfilled || goog.nullFunction, |
+ opt_onRejected || null, |
+ opt_context)); |
+}; |
+ |
+ |
+/** |
+ * Adds a callback that will be invoked when the Promise is settled (fulfilled |
+ * or rejected). The callback receives no argument, and no new child Promise is |
* created. This is useful for ensuring that cleanup takes place after certain |
* asynchronous operations. Callbacks added with {@code thenAlways} will be |
* executed in the same order with other calls to {@code then}, |
@@ -400,34 +630,25 @@ goog.Thenable.addImplementation(goog.Promise); |
* not prevented by adding callbacks with {@code thenAlways}. A Promise that has |
* a cleanup handler added with {@code thenAlways} will be canceled if all of |
* its children created by {@code then} (or {@code thenCatch}) are canceled. |
+ * Additionally, since any rejections are not passed to the callback, it does |
+ * not stop the unhandled rejection handler from running. |
* |
- * @param {function(this:THIS): void} onResolved A function that will be invoked |
- * when the Promise is resolved. |
+ * @param {function(this:THIS): void} onSettled A function that will be invoked |
+ * when the Promise is settled (fulfilled or rejected). |
* @param {THIS=} opt_context An optional context object that will be the |
* execution context for the callbacks. By default, functions are executed |
* in the global scope. |
- * @return {!goog.Promise.<TYPE>} This Promise, for chaining additional calls. |
+ * @return {!goog.Promise<TYPE>} This Promise, for chaining additional calls. |
* @template THIS |
*/ |
-goog.Promise.prototype.thenAlways = function(onResolved, opt_context) { |
+goog.Promise.prototype.thenAlways = function(onSettled, opt_context) { |
if (goog.Promise.LONG_STACK_TRACES) { |
this.addStackTrace_(new Error('thenAlways')); |
} |
- var callback = function() { |
- try { |
- // Ensure that no arguments are passed to onResolved. |
- onResolved.call(opt_context); |
- } catch (err) { |
- goog.Promise.handleRejection_.call(null, err); |
- } |
- }; |
- |
- this.addCallbackEntry_({ |
- child: null, |
- onRejected: callback, |
- onFulfilled: callback |
- }); |
+ var entry = goog.Promise.getCallbackEntry_(onSettled, onSettled, opt_context); |
+ entry.always = true; |
+ this.addCallbackEntry_(entry); |
return this; |
}; |
@@ -486,6 +707,7 @@ goog.Promise.prototype.cancelInternal_ = function(err) { |
if (this.parent_) { |
// Cancel the Promise and remove it from the parent's child list. |
this.parent_.cancelChild_(this, err); |
+ this.parent_ = null; |
} else { |
this.resolve_(goog.Promise.State_.REJECTED, err); |
} |
@@ -508,32 +730,42 @@ goog.Promise.prototype.cancelChild_ = function(childPromise, err) { |
return; |
} |
var childCount = 0; |
- var childIndex = -1; |
+ var childEntry = null; |
+ var beforeChildEntry = null; |
// Find the callback entry for the childPromise, and count whether there are |
// additional child Promises. |
- for (var i = 0, entry; entry = this.callbackEntries_[i]; i++) { |
- var child = entry.child; |
- if (child) { |
+ for (var entry = this.callbackEntries_; entry; entry = entry.next) { |
+ if (!entry.always) { |
childCount++; |
- if (child == childPromise) { |
- childIndex = i; |
+ if (entry.child == childPromise) { |
+ childEntry = entry; |
} |
- if (childIndex >= 0 && childCount > 1) { |
+ if (childEntry && childCount > 1) { |
break; |
} |
} |
+ if (!childEntry) { |
+ beforeChildEntry = entry; |
+ } |
} |
+ // Can a child entry be missing? |
+ |
// If the child Promise was the only child, cancel this Promise as well. |
// Otherwise, reject only the child Promise with the cancel error. |
- if (childIndex >= 0) { |
+ if (childEntry) { |
if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) { |
this.cancelInternal_(err); |
} else { |
- var callbackEntry = this.callbackEntries_.splice(childIndex, 1)[0]; |
+ if (beforeChildEntry) { |
+ this.removeEntryAfter_(beforeChildEntry); |
+ } else { |
+ this.popEntry_(); |
+ } |
+ |
this.executeCallback_( |
- callbackEntry, goog.Promise.State_.REJECTED, err); |
+ childEntry, goog.Promise.State_.REJECTED, err); |
} |
} |
}; |
@@ -541,23 +773,20 @@ goog.Promise.prototype.cancelChild_ = function(childPromise, err) { |
/** |
* Adds a callback entry to the current Promise, and schedules callback |
- * execution if the Promise has already been resolved. |
+ * execution if the Promise has already been settled. |
* |
* @param {goog.Promise.CallbackEntry_} callbackEntry Record containing |
* {@code onFulfilled} and {@code onRejected} callbacks to execute after |
- * the Promise is resolved. |
+ * the Promise is settled. |
* @private |
*/ |
goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) { |
- if ((!this.callbackEntries_ || !this.callbackEntries_.length) && |
+ if (!this.hasEntry_() && |
(this.state_ == goog.Promise.State_.FULFILLED || |
this.state_ == goog.Promise.State_.REJECTED)) { |
this.scheduleCallbacks_(); |
} |
- if (!this.callbackEntries_) { |
- this.callbackEntries_ = []; |
- } |
- this.callbackEntries_.push(callbackEntry); |
+ this.queueEntry_(callbackEntry); |
}; |
@@ -570,7 +799,7 @@ goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) { |
* @see http://promisesaplus.com/#the__method |
* |
* @param {?function(this:THIS, TYPE): |
- * (RESULT|goog.Promise.<RESULT>|Thenable)} onFulfilled A callback that |
+ * (RESULT|goog.Promise<RESULT>|Thenable)} onFulfilled A callback that |
* will be invoked if the Promise is fullfilled, or null. |
* @param {?function(this:THIS, *): *} onRejected A callback that will be |
* invoked if the Promise is rejected, or null. |
@@ -583,11 +812,8 @@ goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) { |
goog.Promise.prototype.addChildPromise_ = function( |
onFulfilled, onRejected, opt_context) { |
- var callbackEntry = { |
- child: null, |
- onFulfilled: null, |
- onRejected: null |
- }; |
+ /** @type {goog.Promise.CallbackEntry_} */ |
+ var callbackEntry = goog.Promise.getCallbackEntry_(null, null, null); |
callbackEntry.child = new goog.Promise(function(resolve, reject) { |
// Invoke onFulfilled, or resolve with the parent's value if absent. |
@@ -618,8 +844,7 @@ goog.Promise.prototype.addChildPromise_ = function( |
}); |
callbackEntry.child.parent_ = this; |
- this.addCallbackEntry_( |
- /** @type {goog.Promise.CallbackEntry_} */ (callbackEntry)); |
+ this.addCallbackEntry_(callbackEntry); |
return callbackEntry.child; |
}; |
@@ -655,11 +880,11 @@ goog.Promise.prototype.unblockAndReject_ = function(reason) { |
* is a no-op if the given Promise has already been resolved. |
* |
* If the given result is a Thenable (such as another Promise), the Promise will |
- * be resolved with the same state and result as the Thenable once it is itself |
- * resolved. |
+ * be settled with the same state and result as the Thenable once it is itself |
+ * settled. |
* |
- * If the given result is not a Thenable, the Promise will be fulfilled or |
- * rejected with that result based on the given state. |
+ * If the given result is not a Thenable, the Promise will be settled (fulfilled |
+ * or rejected) with that result based on the given state. |
* |
* @see http://promisesaplus.com/#the_promise_resolution_procedure |
* |
@@ -675,28 +900,20 @@ goog.Promise.prototype.resolve_ = function(state, x) { |
if (this == x) { |
state = goog.Promise.State_.REJECTED; |
x = new TypeError('Promise cannot resolve to itself'); |
+ } |
- } else if (goog.Thenable.isImplementedBy(x)) { |
- x = /** @type {!goog.Thenable} */ (x); |
- this.state_ = goog.Promise.State_.BLOCKED; |
- x.then(this.unblockAndFulfill_, this.unblockAndReject_, this); |
+ this.state_ = goog.Promise.State_.BLOCKED; |
+ var isThenable = goog.Promise.maybeThen_( |
+ x, this.unblockAndFulfill_, this.unblockAndReject_, this); |
+ if (isThenable) { |
return; |
- |
- } else if (goog.isObject(x)) { |
- try { |
- var then = x['then']; |
- if (goog.isFunction(then)) { |
- this.tryThen_(x, then); |
- return; |
- } |
- } catch (e) { |
- state = goog.Promise.State_.REJECTED; |
- x = e; |
- } |
} |
this.result_ = x; |
this.state_ = state; |
+ // Since we can no longer be canceled, remove link to parent, so that the |
+ // child promise does not keep the parent promise alive. |
+ this.parent_ = null; |
this.scheduleCallbacks_(); |
if (state == goog.Promise.State_.REJECTED && |
@@ -707,6 +924,43 @@ goog.Promise.prototype.resolve_ = function(state, x) { |
/** |
+ * Invokes the "then" method of an input value if that value is a Thenable. This |
+ * is a no-op if the value is not thenable. |
+ * |
+ * @param {*} value A potentially thenable value. |
+ * @param {!Function} onFulfilled |
+ * @param {!Function} onRejected |
+ * @param {*} context |
+ * @return {boolean} Whether the input value was thenable. |
+ * @private |
+ */ |
+goog.Promise.maybeThen_ = function(value, onFulfilled, onRejected, context) { |
+ if (value instanceof goog.Promise) { |
+ value.thenVoid(onFulfilled, onRejected, context); |
+ return true; |
+ } else if (goog.Thenable.isImplementedBy(value)) { |
+ value = /** @type {!goog.Thenable} */ (value); |
+ value.then(onFulfilled, onRejected, context); |
+ return true; |
+ } else if (goog.isObject(value)) { |
+ try { |
+ var then = value['then']; |
+ if (goog.isFunction(then)) { |
+ goog.Promise.tryThen_( |
+ value, then, onFulfilled, onRejected, context); |
+ return true; |
+ } |
+ } catch (e) { |
+ onRejected.call(context, e); |
+ return true; |
+ } |
+ } |
+ |
+ return false; |
+}; |
+ |
+ |
+/** |
* Attempts to call the {@code then} method on an object in the hopes that it is |
* a Promise-compatible instance. This allows interoperation between different |
* Promise implementations, however a non-compliant object may cause a Promise |
@@ -718,24 +972,26 @@ goog.Promise.prototype.resolve_ = function(state, x) { |
* @param {Thenable} thenable An object with a {@code then} method that may be |
* compatible with the Promise/A+ specification. |
* @param {!Function} then The {@code then} method of the Thenable object. |
+ * @param {!Function} onFulfilled |
+ * @param {!Function} onRejected |
+ * @param {*} context |
* @private |
*/ |
-goog.Promise.prototype.tryThen_ = function(thenable, then) { |
- this.state_ = goog.Promise.State_.BLOCKED; |
- var promise = this; |
- var called = false; |
+goog.Promise.tryThen_ = function( |
+ thenable, then, onFulfilled, onRejected, context) { |
+ var called = false; |
var resolve = function(value) { |
if (!called) { |
called = true; |
- promise.unblockAndFulfill_(value); |
+ onFulfilled.call(context, value); |
} |
}; |
var reject = function(reason) { |
if (!called) { |
called = true; |
- promise.unblockAndReject_(reason); |
+ onRejected.call(context, reason); |
} |
}; |
@@ -748,7 +1004,7 @@ goog.Promise.prototype.tryThen_ = function(thenable, then) { |
/** |
- * Executes the pending callbacks of a resolved Promise after a timeout. |
+ * Executes the pending callbacks of a settled Promise after a timeout. |
* |
* Section 2.2.4 of the Promises/A+ specification requires that Promise |
* callbacks must only be invoked from a call stack that only contains Promise |
@@ -772,21 +1028,83 @@ goog.Promise.prototype.scheduleCallbacks_ = function() { |
/** |
+ * @return {boolean} Whether there are any pending callbacks queued. |
+ * @private |
+ */ |
+goog.Promise.prototype.hasEntry_ = function() { |
+ return !!this.callbackEntries_; |
+}; |
+ |
+ |
+/** |
+ * @param {goog.Promise.CallbackEntry_} entry |
+ * @private |
+ */ |
+goog.Promise.prototype.queueEntry_ = function(entry) { |
+ goog.asserts.assert(entry.onFulfilled != null); |
+ |
+ if (this.callbackEntriesTail_) { |
+ this.callbackEntriesTail_.next = entry; |
+ this.callbackEntriesTail_ = entry; |
+ } else { |
+ // It the work queue was empty set the head too. |
+ this.callbackEntries_ = entry; |
+ this.callbackEntriesTail_ = entry; |
+ } |
+}; |
+ |
+ |
+/** |
+ * @return {goog.Promise.CallbackEntry_} entry |
+ * @private |
+ */ |
+goog.Promise.prototype.popEntry_ = function() { |
+ var entry = null; |
+ if (this.callbackEntries_) { |
+ entry = this.callbackEntries_; |
+ this.callbackEntries_ = entry.next; |
+ entry.next = null; |
+ } |
+ // It the work queue is empty clear the tail too. |
+ if (!this.callbackEntries_) { |
+ this.callbackEntriesTail_ = null; |
+ } |
+ |
+ if (entry != null) { |
+ goog.asserts.assert(entry.onFulfilled != null); |
+ } |
+ return entry; |
+}; |
+ |
+ |
+/** |
+ * @param {goog.Promise.CallbackEntry_} previous |
+ * @private |
+ */ |
+goog.Promise.prototype.removeEntryAfter_ = function(previous) { |
+ goog.asserts.assert(this.callbackEntries_); |
+ goog.asserts.assert(previous != null); |
+ // If the last entry is being removed, update the tail |
+ if (previous.next == this.callbackEntriesTail_) { |
+ this.callbackEntriesTail_ = previous; |
+ } |
+ |
+ previous.next = previous.next.next; |
+}; |
+ |
+ |
+/** |
* Executes all pending callbacks for this Promise. |
* |
* @private |
*/ |
goog.Promise.prototype.executeCallbacks_ = function() { |
- while (this.callbackEntries_ && this.callbackEntries_.length) { |
- var entries = this.callbackEntries_; |
- this.callbackEntries_ = []; |
- |
- for (var i = 0; i < entries.length; i++) { |
- if (goog.Promise.LONG_STACK_TRACES) { |
- this.currentStep_++; |
- } |
- this.executeCallback_(entries[i], this.state_, this.result_); |
+ var entry = null; |
+ while (entry = this.popEntry_()) { |
+ if (goog.Promise.LONG_STACK_TRACES) { |
+ this.currentStep_++; |
} |
+ this.executeCallback_(entry, this.state_, this.result_); |
} |
this.executing_ = false; |
}; |
@@ -794,22 +1112,56 @@ goog.Promise.prototype.executeCallbacks_ = function() { |
/** |
* Executes a pending callback for this Promise. Invokes an {@code onFulfilled} |
- * or {@code onRejected} callback based on the resolved state of the Promise. |
+ * or {@code onRejected} callback based on the settled state of the Promise. |
* |
* @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the |
* onFulfilled and/or onRejected callbacks for this step. |
* @param {goog.Promise.State_} state The resolution status of the Promise, |
* either FULFILLED or REJECTED. |
- * @param {*} result The resolved result of the Promise. |
+ * @param {*} result The settled result of the Promise. |
* @private |
*/ |
goog.Promise.prototype.executeCallback_ = function( |
callbackEntry, state, result) { |
- if (state == goog.Promise.State_.FULFILLED) { |
- callbackEntry.onFulfilled(result); |
- } else { |
+ // Cancel an unhandled rejection if the then/thenVoid call had an onRejected. |
+ if (state == goog.Promise.State_.REJECTED && |
+ callbackEntry.onRejected && !callbackEntry.always) { |
this.removeUnhandledRejection_(); |
- callbackEntry.onRejected(result); |
+ } |
+ |
+ if (callbackEntry.child) { |
+ // When the parent is settled, the child no longer needs to hold on to it, |
+ // as the parent can no longer be canceled. |
+ callbackEntry.child.parent_ = null; |
+ goog.Promise.invokeCallback_(callbackEntry, state, result); |
+ } else { |
+ // Callbacks created with thenAlways or thenVoid do not have the rejection |
+ // handling code normally set up in the child Promise. |
+ try { |
+ callbackEntry.always ? |
+ callbackEntry.onFulfilled.call(callbackEntry.context) : |
+ goog.Promise.invokeCallback_(callbackEntry, state, result); |
+ } catch (err) { |
+ goog.Promise.handleRejection_.call(null, err); |
+ } |
+ } |
+ goog.Promise.returnEntry_(callbackEntry); |
+}; |
+ |
+ |
+/** |
+ * Executes the onFulfilled or onRejected callback for a callbackEntry. |
+ * |
+ * @param {!goog.Promise.CallbackEntry_} callbackEntry |
+ * @param {goog.Promise.State_} state |
+ * @param {*} result |
+ * @private |
+ */ |
+goog.Promise.invokeCallback_ = function(callbackEntry, state, result) { |
+ if (state == goog.Promise.State_.FULFILLED) { |
+ callbackEntry.onFulfilled.call(callbackEntry.context, result); |
+ } else if (callbackEntry.onRejected) { |
+ callbackEntry.onRejected.call(callbackEntry.context, result); |
} |
}; |
@@ -964,10 +1316,10 @@ goog.Promise.CancellationError.prototype.name = 'cancel'; |
/** |
* Internal implementation of the resolver interface. |
* |
- * @param {!goog.Promise.<TYPE>} promise |
- * @param {function((TYPE|goog.Promise.<TYPE>|Thenable)=)} resolve |
- * @param {function(*): void} reject |
- * @implements {goog.promise.Resolver.<TYPE>} |
+ * @param {!goog.Promise<TYPE>} promise |
+ * @param {function((TYPE|goog.Promise<TYPE>|Thenable)=)} resolve |
+ * @param {function(*=): void} reject |
+ * @implements {goog.promise.Resolver<TYPE>} |
* @final @struct |
* @constructor |
* @private |