Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(346)

Unified Diff: third_party/google_input_tools/third_party/closure_library/closure/goog/promise/promise.js

Issue 1257313003: Update Google Input Tools (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Free up grd resources. Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698