| 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
|
|
|