Adding mapbox-gl branch
This commit is contained in:
+924
@@ -0,0 +1,924 @@
|
||||
// Copyright 2007 Bob Ippolito. All Rights Reserved.
|
||||
// Modifications Copyright 2009 The Closure Library Authors. All Rights
|
||||
// Reserved.
|
||||
|
||||
/**
|
||||
* @license Portions of this code are from MochiKit, received by
|
||||
* The Closure Authors under the MIT license. All other code is Copyright
|
||||
* 2005-2009 The Closure Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Classes for tracking asynchronous operations and handling the
|
||||
* results. The Deferred object here is patterned after the Deferred object in
|
||||
* the Twisted python networking framework.
|
||||
*
|
||||
* See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
|
||||
*
|
||||
* Based on the Dojo code which in turn is based on the MochiKit code.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
* @author brenneman@google.com (Shawn Brenneman)
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.Deferred');
|
||||
goog.provide('goog.async.Deferred.AlreadyCalledError');
|
||||
goog.provide('goog.async.Deferred.CanceledError');
|
||||
|
||||
goog.require('goog.Promise');
|
||||
goog.require('goog.Thenable');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug.Error');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A Deferred represents the result of an asynchronous operation. A Deferred
|
||||
* instance has no result when it is created, and is "fired" (given an initial
|
||||
* result) by calling {@code callback} or {@code errback}.
|
||||
*
|
||||
* Once fired, the result is passed through a sequence of callback functions
|
||||
* registered with {@code addCallback} or {@code addErrback}. The functions may
|
||||
* mutate the result before it is passed to the next function in the sequence.
|
||||
*
|
||||
* Callbacks and errbacks may be added at any time, including after the Deferred
|
||||
* has been "fired". If there are no pending actions in the execution sequence
|
||||
* of a fired Deferred, any new callback functions will be called with the last
|
||||
* computed result. Adding a callback function is the only way to access the
|
||||
* result of the Deferred.
|
||||
*
|
||||
* If a Deferred operation is canceled, an optional user-provided cancellation
|
||||
* function is invoked which may perform any special cleanup, followed by firing
|
||||
* the Deferred's errback sequence with a {@code CanceledError}. If the
|
||||
* Deferred has already fired, cancellation is ignored.
|
||||
*
|
||||
* Deferreds may be templated to a specific type they produce using generics
|
||||
* with syntax such as:
|
||||
* <code>
|
||||
* /** @type {goog.async.Deferred<string>} */
|
||||
* var d = new goog.async.Deferred();
|
||||
* // Compiler can infer that foo is a string.
|
||||
* d.addCallback(function(foo) {...});
|
||||
* d.callback('string'); // Checked to be passed a string
|
||||
* </code>
|
||||
* Since deferreds are often used to produce different values across a chain,
|
||||
* the type information is not propagated across chains, but rather only
|
||||
* associated with specifically cast objects.
|
||||
*
|
||||
* @param {Function=} opt_onCancelFunction A function that will be called if the
|
||||
* Deferred is canceled. If provided, this function runs before the
|
||||
* Deferred is fired with a {@code CanceledError}.
|
||||
* @param {Object=} opt_defaultScope The default object context to call
|
||||
* callbacks and errbacks in.
|
||||
* @constructor
|
||||
* @implements {goog.Thenable<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
|
||||
/**
|
||||
* Entries in the sequence are arrays containing a callback, an errback, and
|
||||
* an optional scope. The callback or errback in an entry may be null.
|
||||
* @type {!Array<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.sequence_ = [];
|
||||
|
||||
/**
|
||||
* Optional function that will be called if the Deferred is canceled.
|
||||
* @type {Function|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.onCancelFunction_ = opt_onCancelFunction;
|
||||
|
||||
/**
|
||||
* The default scope to execute callbacks and errbacks in.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.defaultScope_ = opt_defaultScope || null;
|
||||
|
||||
/**
|
||||
* Whether the Deferred has been fired.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.fired_ = false;
|
||||
|
||||
/**
|
||||
* Whether the last result in the execution sequence was an error.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.hadError_ = false;
|
||||
|
||||
/**
|
||||
* The current Deferred result, updated as callbacks and errbacks are
|
||||
* executed.
|
||||
* @type {*}
|
||||
* @private
|
||||
*/
|
||||
this.result_ = undefined;
|
||||
|
||||
/**
|
||||
* Whether the Deferred is blocked waiting on another Deferred to fire. If a
|
||||
* callback or errback returns a Deferred as a result, the execution sequence
|
||||
* is blocked until that Deferred result becomes available.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.blocked_ = false;
|
||||
|
||||
/**
|
||||
* Whether this Deferred is blocking execution of another Deferred. If this
|
||||
* instance was returned as a result in another Deferred's execution
|
||||
* sequence,that other Deferred becomes blocked until this instance's
|
||||
* execution sequence completes. No additional callbacks may be added to a
|
||||
* Deferred once it is blocking another instance.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.blocking_ = false;
|
||||
|
||||
/**
|
||||
* Whether the Deferred has been canceled without having a custom cancel
|
||||
* function.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.silentlyCanceled_ = false;
|
||||
|
||||
/**
|
||||
* If an error is thrown during Deferred execution with no errback to catch
|
||||
* it, the error is rethrown after a timeout. Reporting the error after a
|
||||
* timeout allows execution to continue in the calling context (empty when
|
||||
* no error is scheduled).
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.unhandledErrorId_ = 0;
|
||||
|
||||
/**
|
||||
* If this Deferred was created by branch(), this will be the "parent"
|
||||
* Deferred.
|
||||
* @type {goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
this.parent_ = null;
|
||||
|
||||
/**
|
||||
* The number of Deferred objects that have been branched off this one. This
|
||||
* will be decremented whenever a branch is fired or canceled.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.branches_ = 0;
|
||||
|
||||
if (goog.async.Deferred.LONG_STACK_TRACES) {
|
||||
/**
|
||||
* Holds the stack trace at time of deferred creation if the JS engine
|
||||
* provides the Error.captureStackTrace API.
|
||||
* @private {?string}
|
||||
*/
|
||||
this.constructorStack_ = null;
|
||||
if (Error.captureStackTrace) {
|
||||
var target = { stack: '' };
|
||||
Error.captureStackTrace(target, goog.async.Deferred);
|
||||
// Check if Error.captureStackTrace worked. It fails in gjstest.
|
||||
if (typeof target.stack == 'string') {
|
||||
// Remove first line and force stringify to prevent memory leak due to
|
||||
// holding on to actual stack frames.
|
||||
this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether unhandled errors should always get rethrown to the
|
||||
* global scope. Defaults to the value of goog.DEBUG.
|
||||
*/
|
||||
goog.define('goog.async.Deferred.STRICT_ERRORS', false);
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to attempt to make stack traces long. Defaults to
|
||||
* the value of goog.DEBUG.
|
||||
*/
|
||||
goog.define('goog.async.Deferred.LONG_STACK_TRACES', false);
|
||||
|
||||
|
||||
/**
|
||||
* Cancels a Deferred that has not yet been fired, or is blocked on another
|
||||
* deferred operation. If this Deferred is waiting for a blocking Deferred to
|
||||
* fire, the blocking Deferred will also be canceled.
|
||||
*
|
||||
* If this Deferred was created by calling branch() on a parent Deferred with
|
||||
* opt_propagateCancel set to true, the parent may also be canceled. If
|
||||
* opt_deepCancel is set, cancel() will be called on the parent (as well as any
|
||||
* other ancestors if the parent is also a branch). If one or more branches were
|
||||
* created with opt_propagateCancel set to true, the parent will be canceled if
|
||||
* cancel() is called on all of those branches.
|
||||
*
|
||||
* @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
|
||||
* if cancel() hasn't been called on some of the parent's branches. Has no
|
||||
* effect on a branch without opt_propagateCancel set to true.
|
||||
*/
|
||||
goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
|
||||
if (!this.hasFired()) {
|
||||
if (this.parent_) {
|
||||
// Get rid of the parent reference before potentially running the parent's
|
||||
// canceler function to ensure that this cancellation isn't
|
||||
// double-counted.
|
||||
var parent = this.parent_;
|
||||
delete this.parent_;
|
||||
if (opt_deepCancel) {
|
||||
parent.cancel(opt_deepCancel);
|
||||
} else {
|
||||
parent.branchCancel_();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.onCancelFunction_) {
|
||||
// Call in user-specified scope.
|
||||
this.onCancelFunction_.call(this.defaultScope_, this);
|
||||
} else {
|
||||
this.silentlyCanceled_ = true;
|
||||
}
|
||||
if (!this.hasFired()) {
|
||||
this.errback(new goog.async.Deferred.CanceledError(this));
|
||||
}
|
||||
} else if (this.result_ instanceof goog.async.Deferred) {
|
||||
this.result_.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle a single branch being canceled. Once all branches are canceled, this
|
||||
* Deferred will be canceled as well.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.prototype.branchCancel_ = function() {
|
||||
this.branches_--;
|
||||
if (this.branches_ <= 0) {
|
||||
this.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called after a blocking Deferred fires. Unblocks this Deferred and resumes
|
||||
* its execution sequence.
|
||||
*
|
||||
* @param {boolean} isSuccess Whether the result is a success or an error.
|
||||
* @param {*} res The result of the blocking Deferred.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
|
||||
this.blocked_ = false;
|
||||
this.updateResult_(isSuccess, res);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Updates the current result based on the success or failure of the last action
|
||||
* in the execution sequence.
|
||||
*
|
||||
* @param {boolean} isSuccess Whether the new result is a success or an error.
|
||||
* @param {*} res The result.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
|
||||
this.fired_ = true;
|
||||
this.result_ = res;
|
||||
this.hadError_ = !isSuccess;
|
||||
this.fire_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the Deferred has not yet been fired.
|
||||
*
|
||||
* @private
|
||||
* @throws {Error} If this has already been fired.
|
||||
*/
|
||||
goog.async.Deferred.prototype.check_ = function() {
|
||||
if (this.hasFired()) {
|
||||
if (!this.silentlyCanceled_) {
|
||||
throw new goog.async.Deferred.AlreadyCalledError(this);
|
||||
}
|
||||
this.silentlyCanceled_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fire the execution sequence for this Deferred by passing the starting result
|
||||
* to the first registered callback.
|
||||
* @param {VALUE=} opt_result The starting result.
|
||||
*/
|
||||
goog.async.Deferred.prototype.callback = function(opt_result) {
|
||||
this.check_();
|
||||
this.assertNotDeferred_(opt_result);
|
||||
this.updateResult_(true /* isSuccess */, opt_result);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fire the execution sequence for this Deferred by passing the starting error
|
||||
* result to the first registered errback.
|
||||
* @param {*=} opt_result The starting error.
|
||||
*/
|
||||
goog.async.Deferred.prototype.errback = function(opt_result) {
|
||||
this.check_();
|
||||
this.assertNotDeferred_(opt_result);
|
||||
this.makeStackTraceLong_(opt_result);
|
||||
this.updateResult_(false /* isSuccess */, opt_result);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to make the error's stack trace be long in that it contains the
|
||||
* stack trace from the point where the deferred was created on top of the
|
||||
* current stack trace to give additional context.
|
||||
* @param {*} error
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
|
||||
if (!goog.async.Deferred.LONG_STACK_TRACES) {
|
||||
return;
|
||||
}
|
||||
if (this.constructorStack_ && goog.isObject(error) && error.stack &&
|
||||
// Stack looks like it was system generated. See
|
||||
// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
|
||||
(/^[^\n]+(\n [^\n]+)+/).test(error.stack)) {
|
||||
error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
|
||||
this.constructorStack_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that an object is not a Deferred.
|
||||
* @param {*} obj The object to test.
|
||||
* @throws {Error} Throws an exception if the object is a Deferred.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
|
||||
goog.asserts.assert(
|
||||
!(obj instanceof goog.async.Deferred),
|
||||
'An execution sequence may not be initiated with a blocking Deferred.');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Register a callback function to be called with a successful result. If no
|
||||
* value is returned by the callback function, the result value is unchanged. If
|
||||
* a new value is returned, it becomes the Deferred result and will be passed to
|
||||
* the next callback in the execution sequence.
|
||||
*
|
||||
* If the function throws an error, the error becomes the new result and will be
|
||||
* passed to the next errback in the execution chain.
|
||||
*
|
||||
* If the function returns a Deferred, the execution sequence will be blocked
|
||||
* until that Deferred fires. Its result will be passed to the next callback (or
|
||||
* errback if it is an error result) in this Deferred's execution sequence.
|
||||
*
|
||||
* @param {!function(this:T,VALUE):?} cb The function to be called with a
|
||||
* successful result.
|
||||
* @param {T=} opt_scope An optional scope to call the callback in.
|
||||
* @return {!goog.async.Deferred} This Deferred.
|
||||
* @template T
|
||||
*/
|
||||
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
|
||||
return this.addCallbacks(cb, null, opt_scope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Register a callback function to be called with an error result. If no value
|
||||
* is returned by the function, the error result is unchanged. If a new error
|
||||
* value is returned or thrown, that error becomes the Deferred result and will
|
||||
* be passed to the next errback in the execution sequence.
|
||||
*
|
||||
* If the errback function handles the error by returning a non-error value,
|
||||
* that result will be passed to the next normal callback in the sequence.
|
||||
*
|
||||
* If the function returns a Deferred, the execution sequence will be blocked
|
||||
* until that Deferred fires. Its result will be passed to the next callback (or
|
||||
* errback if it is an error result) in this Deferred's execution sequence.
|
||||
*
|
||||
* @param {!function(this:T,?):?} eb The function to be called on an
|
||||
* unsuccessful result.
|
||||
* @param {T=} opt_scope An optional scope to call the errback in.
|
||||
* @return {!goog.async.Deferred<VALUE>} This Deferred.
|
||||
* @template T
|
||||
*/
|
||||
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
|
||||
return this.addCallbacks(null, eb, opt_scope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Registers one function as both a callback and errback.
|
||||
*
|
||||
* @param {!function(this:T,?):?} f The function to be called on any result.
|
||||
* @param {T=} opt_scope An optional scope to call the function in.
|
||||
* @return {!goog.async.Deferred} This Deferred.
|
||||
* @template T
|
||||
*/
|
||||
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
|
||||
return this.addCallbacks(f, f, opt_scope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Registers a callback function and an errback function at the same position
|
||||
* in the execution sequence. Only one of these functions will execute,
|
||||
* depending on the error state during the execution sequence.
|
||||
*
|
||||
* NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
|
||||
* the callback is invoked, the errback will be skipped, and vice versa.
|
||||
*
|
||||
* @param {(function(this:T,VALUE):?)|null} cb The function to be called on a
|
||||
* successful result.
|
||||
* @param {(function(this:T,?):?)|null} eb The function to be called on an
|
||||
* unsuccessful result.
|
||||
* @param {T=} opt_scope An optional scope to call the functions in.
|
||||
* @return {!goog.async.Deferred} This Deferred.
|
||||
* @template T
|
||||
*/
|
||||
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
|
||||
goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
|
||||
this.sequence_.push([cb, eb, opt_scope]);
|
||||
if (this.hasFired()) {
|
||||
this.fire_();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements {@see goog.Thenable} for seamless integration with
|
||||
* {@see goog.Promise}.
|
||||
* Deferred results are mutable and may represent multiple values over
|
||||
* their lifetime. Calling {@code then} on a Deferred returns a Promise
|
||||
* with the result of the Deferred at that point in its callback chain.
|
||||
* Note that if the Deferred result is never mutated, and only
|
||||
* {@code then} calls are made, the Deferred will behave like a Promise.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected,
|
||||
opt_context) {
|
||||
var resolve, reject;
|
||||
var promise = new goog.Promise(function(res, rej) {
|
||||
// Copying resolvers to outer scope, so that they are available when the
|
||||
// deferred callback fires (which may be synchronous).
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
this.addCallbacks(resolve, function(reason) {
|
||||
if (reason instanceof goog.async.Deferred.CanceledError) {
|
||||
promise.cancel();
|
||||
} else {
|
||||
reject(reason);
|
||||
}
|
||||
});
|
||||
return promise.then(opt_onFulfilled, opt_onRejected, opt_context);
|
||||
};
|
||||
goog.Thenable.addImplementation(goog.async.Deferred);
|
||||
|
||||
|
||||
/**
|
||||
* Links another Deferred to the end of this Deferred's execution sequence. The
|
||||
* result of this execution sequence will be passed as the starting result for
|
||||
* the chained Deferred, invoking either its first callback or errback.
|
||||
*
|
||||
* @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
|
||||
* @return {!goog.async.Deferred} This Deferred.
|
||||
*/
|
||||
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
|
||||
this.addCallbacks(
|
||||
otherDeferred.callback, otherDeferred.errback, otherDeferred);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Makes this Deferred wait for another Deferred's execution sequence to
|
||||
* complete before continuing.
|
||||
*
|
||||
* This is equivalent to adding a callback that returns {@code otherDeferred},
|
||||
* but doesn't prevent additional callbacks from being added to
|
||||
* {@code otherDeferred}.
|
||||
*
|
||||
* @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred
|
||||
* to wait for.
|
||||
* @return {!goog.async.Deferred} This Deferred.
|
||||
*/
|
||||
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
|
||||
if (!(otherDeferred instanceof goog.async.Deferred)) {
|
||||
// The Thenable case.
|
||||
return this.addCallback(function() {
|
||||
return otherDeferred;
|
||||
});
|
||||
}
|
||||
return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a branch off this Deferred's execution sequence, and returns it as a
|
||||
* new Deferred. The branched Deferred's starting result will be shared with the
|
||||
* parent at the point of the branch, even if further callbacks are added to the
|
||||
* parent.
|
||||
*
|
||||
* All branches at the same stage in the execution sequence will receive the
|
||||
* same starting value.
|
||||
*
|
||||
* @param {boolean=} opt_propagateCancel If cancel() is called on every child
|
||||
* branch created with opt_propagateCancel, the parent will be canceled as
|
||||
* well.
|
||||
* @return {!goog.async.Deferred<VALUE>} A Deferred that will be started with
|
||||
* the computed result from this stage in the execution sequence.
|
||||
*/
|
||||
goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
|
||||
var d = new goog.async.Deferred();
|
||||
this.chainDeferred(d);
|
||||
if (opt_propagateCancel) {
|
||||
d.parent_ = this;
|
||||
this.branches_++;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the execution sequence has been started on this
|
||||
* Deferred by invoking {@code callback} or {@code errback}.
|
||||
*/
|
||||
goog.async.Deferred.prototype.hasFired = function() {
|
||||
return this.fired_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {*} res The latest result in the execution sequence.
|
||||
* @return {boolean} Whether the current result is an error that should cause
|
||||
* the next errback to fire. May be overridden by subclasses to handle
|
||||
* special error types.
|
||||
* @protected
|
||||
*/
|
||||
goog.async.Deferred.prototype.isError = function(res) {
|
||||
return res instanceof Error;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether an errback exists in the remaining sequence.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.prototype.hasErrback_ = function() {
|
||||
return goog.array.some(this.sequence_, function(sequenceRow) {
|
||||
// The errback is the second element in the array.
|
||||
return goog.isFunction(sequenceRow[1]);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Exhausts the execution sequence while a result is available. The result may
|
||||
* be modified by callbacks or errbacks, and execution will block if the
|
||||
* returned result is an incomplete Deferred.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.prototype.fire_ = function() {
|
||||
if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) {
|
||||
// It is possible to add errbacks after the Deferred has fired. If a new
|
||||
// errback is added immediately after the Deferred encountered an unhandled
|
||||
// error, but before that error is rethrown, the error is unscheduled.
|
||||
goog.async.Deferred.unscheduleError_(this.unhandledErrorId_);
|
||||
this.unhandledErrorId_ = 0;
|
||||
}
|
||||
|
||||
if (this.parent_) {
|
||||
this.parent_.branches_--;
|
||||
delete this.parent_;
|
||||
}
|
||||
|
||||
var res = this.result_;
|
||||
var unhandledException = false;
|
||||
var isNewlyBlocked = false;
|
||||
|
||||
while (this.sequence_.length && !this.blocked_) {
|
||||
var sequenceEntry = this.sequence_.shift();
|
||||
|
||||
var callback = sequenceEntry[0];
|
||||
var errback = sequenceEntry[1];
|
||||
var scope = sequenceEntry[2];
|
||||
|
||||
var f = this.hadError_ ? errback : callback;
|
||||
if (f) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var ret = f.call(scope || this.defaultScope_, res);
|
||||
|
||||
// If no result, then use previous result.
|
||||
if (goog.isDef(ret)) {
|
||||
// Bubble up the error as long as the return value hasn't changed.
|
||||
this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
|
||||
this.result_ = res = ret;
|
||||
}
|
||||
|
||||
if (goog.Thenable.isImplementedBy(res)) {
|
||||
isNewlyBlocked = true;
|
||||
this.blocked_ = true;
|
||||
}
|
||||
|
||||
} catch (ex) {
|
||||
res = ex;
|
||||
this.hadError_ = true;
|
||||
this.makeStackTraceLong_(res);
|
||||
|
||||
if (!this.hasErrback_()) {
|
||||
// If an error is thrown with no additional errbacks in the queue,
|
||||
// prepare to rethrow the error.
|
||||
unhandledException = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.result_ = res;
|
||||
|
||||
if (isNewlyBlocked) {
|
||||
var onCallback = goog.bind(this.continue_, this, true /* isSuccess */);
|
||||
var onErrback = goog.bind(this.continue_, this, false /* isSuccess */);
|
||||
|
||||
if (res instanceof goog.async.Deferred) {
|
||||
res.addCallbacks(onCallback, onErrback);
|
||||
res.blocking_ = true;
|
||||
} else {
|
||||
res.then(onCallback, onErrback);
|
||||
}
|
||||
} else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
|
||||
!(res instanceof goog.async.Deferred.CanceledError)) {
|
||||
this.hadError_ = true;
|
||||
unhandledException = true;
|
||||
}
|
||||
|
||||
if (unhandledException) {
|
||||
// Rethrow the unhandled error after a timeout. Execution will continue, but
|
||||
// the error will be seen by global handlers and the user. The throw will
|
||||
// be canceled if another errback is appended before the timeout executes.
|
||||
// The error's original stack trace is preserved where available.
|
||||
this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Deferred that has an initial result.
|
||||
*
|
||||
* @param {*=} opt_result The result.
|
||||
* @return {!goog.async.Deferred} The new Deferred.
|
||||
*/
|
||||
goog.async.Deferred.succeed = function(opt_result) {
|
||||
var d = new goog.async.Deferred();
|
||||
d.callback(opt_result);
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Deferred that fires when the given promise resolves.
|
||||
* Use only during migration to Promises.
|
||||
*
|
||||
* @param {!goog.Promise<T>} promise
|
||||
* @return {!goog.async.Deferred<T>} The new Deferred.
|
||||
* @template T
|
||||
*/
|
||||
goog.async.Deferred.fromPromise = function(promise) {
|
||||
var d = new goog.async.Deferred();
|
||||
d.callback();
|
||||
d.addCallback(function() {
|
||||
return promise;
|
||||
});
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Deferred that has an initial error result.
|
||||
*
|
||||
* @param {*} res The error result.
|
||||
* @return {!goog.async.Deferred} The new Deferred.
|
||||
*/
|
||||
goog.async.Deferred.fail = function(res) {
|
||||
var d = new goog.async.Deferred();
|
||||
d.errback(res);
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Deferred that has already been canceled.
|
||||
*
|
||||
* @return {!goog.async.Deferred} The new Deferred.
|
||||
*/
|
||||
goog.async.Deferred.canceled = function() {
|
||||
var d = new goog.async.Deferred();
|
||||
d.cancel();
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes values that may or may not be Deferreds.
|
||||
*
|
||||
* If the input value is a Deferred, the Deferred is branched (so the original
|
||||
* execution sequence is not modified) and the input callback added to the new
|
||||
* branch. The branch is returned to the caller.
|
||||
*
|
||||
* If the input value is not a Deferred, the callback will be executed
|
||||
* immediately and an already firing Deferred will be returned to the caller.
|
||||
*
|
||||
* In the following (contrived) example, if <code>isImmediate</code> is true
|
||||
* then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
|
||||
*
|
||||
* <pre>
|
||||
* var value;
|
||||
* if (isImmediate) {
|
||||
* value = 3;
|
||||
* } else {
|
||||
* value = new goog.async.Deferred();
|
||||
* setTimeout(function() { value.callback(6); }, 2000);
|
||||
* }
|
||||
*
|
||||
* var d = goog.async.Deferred.when(value, alert);
|
||||
* </pre>
|
||||
*
|
||||
* @param {*} value Deferred or normal value to pass to the callback.
|
||||
* @param {!function(this:T, ?):?} callback The callback to execute.
|
||||
* @param {T=} opt_scope An optional scope to call the callback in.
|
||||
* @return {!goog.async.Deferred} A new Deferred that will call the input
|
||||
* callback with the input value.
|
||||
* @template T
|
||||
*/
|
||||
goog.async.Deferred.when = function(value, callback, opt_scope) {
|
||||
if (value instanceof goog.async.Deferred) {
|
||||
return value.branch(true).addCallback(callback, opt_scope);
|
||||
} else {
|
||||
return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An error sub class that is used when a Deferred has already been called.
|
||||
* @param {!goog.async.Deferred} deferred The Deferred.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.async.Deferred.AlreadyCalledError = function(deferred) {
|
||||
goog.debug.Error.call(this);
|
||||
|
||||
/**
|
||||
* The Deferred that raised this error.
|
||||
* @type {goog.async.Deferred}
|
||||
*/
|
||||
this.deferred = deferred;
|
||||
};
|
||||
goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.Deferred.AlreadyCalledError.prototype.message =
|
||||
'Deferred has already fired';
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An error sub class that is used when a Deferred is canceled.
|
||||
*
|
||||
* @param {!goog.async.Deferred} deferred The Deferred object.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.async.Deferred.CanceledError = function(deferred) {
|
||||
goog.debug.Error.call(this);
|
||||
|
||||
/**
|
||||
* The Deferred that raised this error.
|
||||
* @type {goog.async.Deferred}
|
||||
*/
|
||||
this.deferred = deferred;
|
||||
};
|
||||
goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper around errors that are scheduled to be thrown by failing deferreds
|
||||
* after a timeout.
|
||||
*
|
||||
* @param {*} error Error from a failing deferred.
|
||||
* @constructor
|
||||
* @final
|
||||
* @private
|
||||
* @struct
|
||||
*/
|
||||
goog.async.Deferred.Error_ = function(error) {
|
||||
/** @const @private {number} */
|
||||
this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0);
|
||||
|
||||
/** @const @private {*} */
|
||||
this.error_ = error;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Actually throws the error and removes it from the list of pending
|
||||
* deferred errors.
|
||||
*/
|
||||
goog.async.Deferred.Error_.prototype.throwError = function() {
|
||||
goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_],
|
||||
'Cannot throw an error that is not scheduled.');
|
||||
delete goog.async.Deferred.errorMap_[this.id_];
|
||||
throw this.error_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the error throw timer.
|
||||
*/
|
||||
goog.async.Deferred.Error_.prototype.resetTimer = function() {
|
||||
goog.global.clearTimeout(this.id_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Map of unhandled errors scheduled to be rethrown in a future timestep.
|
||||
* @private {!Object<number|string, goog.async.Deferred.Error_>}
|
||||
*/
|
||||
goog.async.Deferred.errorMap_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Schedules an error to be thrown after a delay.
|
||||
* @param {*} error Error from a failing deferred.
|
||||
* @return {number} Id of the error.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.scheduleError_ = function(error) {
|
||||
var deferredError = new goog.async.Deferred.Error_(error);
|
||||
goog.async.Deferred.errorMap_[deferredError.id_] = deferredError;
|
||||
return deferredError.id_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unschedules an error from being thrown.
|
||||
* @param {number} id Id of the deferred error to unschedule.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Deferred.unscheduleError_ = function(id) {
|
||||
var error = goog.async.Deferred.errorMap_[id];
|
||||
if (error) {
|
||||
error.resetTimer();
|
||||
delete goog.async.Deferred.errorMap_[id];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that there are no pending deferred errors. If there are any
|
||||
* scheduled errors, one will be thrown immediately to make this function fail.
|
||||
*/
|
||||
goog.async.Deferred.assertNoErrors = function() {
|
||||
var map = goog.async.Deferred.errorMap_;
|
||||
for (var key in map) {
|
||||
var error = map[key];
|
||||
error.resetTimer();
|
||||
error.throwError();
|
||||
}
|
||||
};
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||||
-->
|
||||
<head>
|
||||
<title>Closure Unit Tests - goog.async.Deferred</title>
|
||||
<script src="../../../../../closure/goog/base.js"></script>
|
||||
<script>
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var realSetTimeout = window.setTimeout;
|
||||
var mockClock = new goog.testing.MockClock();
|
||||
|
||||
function setUp() {
|
||||
mockClock.install();
|
||||
goog.async.Deferred.LONG_STACK_TRACES = true;
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
// Advance the mockClock to fire any unhandled exception timeouts.
|
||||
mockClock.tick();
|
||||
mockClock.uninstall();
|
||||
}
|
||||
|
||||
function testErrorStack() {
|
||||
if (!Error.captureStackTrace) {
|
||||
return;
|
||||
}
|
||||
var d;
|
||||
// Get the deferred from somewhere deep in the callstack.
|
||||
(function immediate() {
|
||||
(function immediate2() {
|
||||
d = new goog.async.Deferred();
|
||||
d.addCallback(function actuallyThrows() {
|
||||
throw new Error('Foo');
|
||||
});
|
||||
})();
|
||||
})();
|
||||
d.addCallback(function actuallyThrows() {
|
||||
throw new Error('Foo');
|
||||
});
|
||||
asyncTestCase.waitForAsync('Wait for timeout');
|
||||
realSetTimeout(function willThrow() {
|
||||
var error = assertThrows(function callbackCaller() {
|
||||
d.callback();
|
||||
mockClock.tick();
|
||||
});
|
||||
assertContains('Foo', error.stack);
|
||||
assertContains('testErrorStack', error.stack);
|
||||
assertContains('callbackCaller', error.stack);
|
||||
assertContains('willThrow', error.stack);
|
||||
assertContains('actuallyThrows', error.stack);
|
||||
assertContains('DEFERRED OPERATION', error.stack);
|
||||
assertContains('immediate', error.stack);
|
||||
assertContains('immediate2', error.stack);
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function testErrorStack_forErrback() {
|
||||
if (!Error.captureStackTrace) {
|
||||
return;
|
||||
}
|
||||
var d = new goog.async.Deferred();
|
||||
asyncTestCase.waitForAsync('Wait for timeout');
|
||||
realSetTimeout(function willThrow() {
|
||||
d.errback(new Error('Foo'));
|
||||
asyncTestCase.continueTesting();
|
||||
}, 0);
|
||||
|
||||
d.addErrback(function(error) {
|
||||
assertContains('Foo', error.stack);
|
||||
assertContains('testErrorStack_forErrback', error.stack);
|
||||
assertContains('willThrow', error.stack);
|
||||
assertContains('DEFERRED OPERATION', error.stack);
|
||||
});
|
||||
}
|
||||
|
||||
function testErrorStack_nested() {
|
||||
if (!Error.captureStackTrace) {
|
||||
return;
|
||||
}
|
||||
var d = new goog.async.Deferred();
|
||||
d.addErrback(function(error) {
|
||||
assertContains('Foo', error.stack);
|
||||
assertContains('testErrorStack_nested', error.stack);
|
||||
assertContains('async1', error.stack);
|
||||
assertContains('async2', error.stack);
|
||||
assertContains('immediate', error.stack);
|
||||
assertContains('DEFERRED OPERATION', error.stack);
|
||||
});
|
||||
asyncTestCase.waitForAsync('Wait for timeout');
|
||||
realSetTimeout(function async1() {
|
||||
var nested = new goog.async.Deferred();
|
||||
nested.addErrback(function nestedErrback(error) {
|
||||
d.errback(error);
|
||||
mockClock.tick();
|
||||
});
|
||||
realSetTimeout(function async2() {
|
||||
(function immediate() {
|
||||
nested.errback(new Error('Foo'));
|
||||
mockClock.tick();
|
||||
})();
|
||||
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function testErrorStack_doesNotTouchCustomStack() {
|
||||
if (!Error.captureStackTrace) {
|
||||
return;
|
||||
}
|
||||
var d = new goog.async.Deferred();
|
||||
d.addCallback(function actuallyThrows() {
|
||||
var e = new Error('Foo');
|
||||
e.stack = 'STACK';
|
||||
throw e;
|
||||
});
|
||||
asyncTestCase.waitForAsync('Wait for timeout');
|
||||
realSetTimeout(function willThrow() {
|
||||
var error = assertThrows(function callbackCaller() {
|
||||
d.callback();
|
||||
mockClock.tick();
|
||||
});
|
||||
assertContains('STACK', error.stack);
|
||||
asyncTestCase.continueTesting();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+1070
File diff suppressed because it is too large
Load Diff
+206
@@ -0,0 +1,206 @@
|
||||
// Copyright 2005 Bob Ippolito. All Rights Reserved.
|
||||
// Modifications Copyright 2009 The Closure Library Authors.
|
||||
// All Rights Reserved.
|
||||
|
||||
/**
|
||||
* Portions of this code are from MochiKit, received by The Closure
|
||||
* Library Authors under the MIT license. All other code is Copyright
|
||||
* 2005-2009 The Closure Library Authors. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for tracking multiple asynchronous operations and
|
||||
* handling the results. The DeferredList object here is patterned after the
|
||||
* DeferredList object in the Twisted python networking framework.
|
||||
*
|
||||
* Based on the MochiKit code.
|
||||
*
|
||||
* See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
|
||||
*
|
||||
* @author brenneman@google.com (Shawn Brenneman)
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.DeferredList');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an object that waits on the results of multiple asynchronous
|
||||
* operations and marshals the results. It is itself a <code>Deferred</code>,
|
||||
* and may have an execution sequence of callback functions added to it. Each
|
||||
* <code>DeferredList</code> instance is single use and may be fired only once.
|
||||
*
|
||||
* The default behavior of a <code>DeferredList</code> is to wait for a success
|
||||
* or error result from every <code>Deferred</code> in its input list. Once
|
||||
* every result is available, the <code>DeferredList</code>'s execution sequence
|
||||
* is fired with a list of <code>[success, result]</code> array pairs, where
|
||||
* <code>success</code> is a boolean indicating whether <code>result</code> was
|
||||
* the product of a callback or errback. The list's completion criteria and
|
||||
* result list may be modified by setting one or more of the boolean options
|
||||
* documented below.
|
||||
*
|
||||
* <code>Deferred</code> instances passed into a <code>DeferredList</code> are
|
||||
* independent, and may have additional callbacks and errbacks added to their
|
||||
* execution sequences after they are passed as inputs to the list.
|
||||
*
|
||||
* @param {!Array<!goog.async.Deferred>} list An array of deferred results to
|
||||
* wait for.
|
||||
* @param {boolean=} opt_fireOnOneCallback Whether to stop waiting as soon as
|
||||
* one input completes successfully. In this case, the
|
||||
* <code>DeferredList</code>'s callback chain will be called with a two
|
||||
* element array, <code>[index, result]</code>, where <code>index</code>
|
||||
* identifies which input <code>Deferred</code> produced the successful
|
||||
* <code>result</code>.
|
||||
* @param {boolean=} opt_fireOnOneErrback Whether to stop waiting as soon as one
|
||||
* input reports an error. The failing result is passed to the
|
||||
* <code>DeferredList</code>'s errback sequence.
|
||||
* @param {boolean=} opt_consumeErrors When true, any errors fired by a
|
||||
* <code>Deferred</code> in the input list will be captured and replaced
|
||||
* with a succeeding null result. Any callbacks added to the
|
||||
* <code>Deferred</code> after its use in the <code>DeferredList</code> will
|
||||
* receive null instead of the error.
|
||||
* @param {Function=} opt_canceler A function that will be called if the
|
||||
* <code>DeferredList</code> is canceled. @see goog.async.Deferred#cancel
|
||||
* @param {Object=} opt_defaultScope The default scope to invoke callbacks or
|
||||
* errbacks in.
|
||||
* @constructor
|
||||
* @extends {goog.async.Deferred}
|
||||
*/
|
||||
goog.async.DeferredList = function(
|
||||
list, opt_fireOnOneCallback, opt_fireOnOneErrback, opt_consumeErrors,
|
||||
opt_canceler, opt_defaultScope) {
|
||||
|
||||
goog.async.DeferredList.base(this, 'constructor',
|
||||
opt_canceler, opt_defaultScope);
|
||||
|
||||
/**
|
||||
* The list of Deferred objects to wait for.
|
||||
* @const {!Array<!goog.async.Deferred>}
|
||||
* @private
|
||||
*/
|
||||
this.list_ = list;
|
||||
|
||||
/**
|
||||
* The stored return values of the Deferred objects.
|
||||
* @const {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.deferredResults_ = [];
|
||||
|
||||
/**
|
||||
* Whether to fire on the first successful callback instead of waiting for
|
||||
* every Deferred to complete.
|
||||
* @const {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.fireOnOneCallback_ = !!opt_fireOnOneCallback;
|
||||
|
||||
/**
|
||||
* Whether to fire on the first error result received instead of waiting for
|
||||
* every Deferred to complete.
|
||||
* @const {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.fireOnOneErrback_ = !!opt_fireOnOneErrback;
|
||||
|
||||
/**
|
||||
* Whether to stop error propagation on the input Deferred objects. If the
|
||||
* DeferredList sees an error from one of the Deferred inputs, the error will
|
||||
* be captured, and the Deferred will be returned to success state with a null
|
||||
* return value.
|
||||
* @const {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.consumeErrors_ = !!opt_consumeErrors;
|
||||
|
||||
/**
|
||||
* The number of input deferred objects that have fired.
|
||||
* @private {number}
|
||||
*/
|
||||
this.numFinished_ = 0;
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var d = list[i];
|
||||
d.addCallbacks(goog.bind(this.handleCallback_, this, i, true),
|
||||
goog.bind(this.handleCallback_, this, i, false));
|
||||
}
|
||||
|
||||
if (list.length == 0 && !this.fireOnOneCallback_) {
|
||||
this.callback(this.deferredResults_);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.async.DeferredList, goog.async.Deferred);
|
||||
|
||||
|
||||
/**
|
||||
* Registers the result from an input deferred callback or errback. The result
|
||||
* is returned and may be passed to additional handlers in the callback chain.
|
||||
*
|
||||
* @param {number} index The index of the firing deferred object in the input
|
||||
* list.
|
||||
* @param {boolean} success Whether the result is from a callback or errback.
|
||||
* @param {*} result The result of the callback or errback.
|
||||
* @return {*} The result, to be handled by the next handler in the deferred's
|
||||
* callback chain (if any). If consumeErrors is set, an error result is
|
||||
* replaced with null.
|
||||
* @private
|
||||
*/
|
||||
goog.async.DeferredList.prototype.handleCallback_ = function(
|
||||
index, success, result) {
|
||||
|
||||
this.numFinished_++;
|
||||
this.deferredResults_[index] = [success, result];
|
||||
|
||||
if (!this.hasFired()) {
|
||||
if (this.fireOnOneCallback_ && success) {
|
||||
this.callback([index, result]);
|
||||
} else if (this.fireOnOneErrback_ && !success) {
|
||||
this.errback(result);
|
||||
} else if (this.numFinished_ == this.list_.length) {
|
||||
this.callback(this.deferredResults_);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.consumeErrors_ && !success) {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.DeferredList.prototype.errback = function(res) {
|
||||
goog.async.DeferredList.base(this, 'errback', res);
|
||||
|
||||
// On error, cancel any pending requests.
|
||||
for (var i = 0; i < this.list_.length; i++) {
|
||||
this.list_[i].cancel();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a <code>DeferredList</code> that gathers results from multiple
|
||||
* <code>Deferred</code> inputs. If all inputs succeed, the callback is fired
|
||||
* with the list of results as a flat array. If any input fails, the list's
|
||||
* errback is fired immediately with the offending error, and all other pending
|
||||
* inputs are canceled.
|
||||
*
|
||||
* @param {!Array<!goog.async.Deferred>} list The list of <code>Deferred</code>
|
||||
* inputs to wait for.
|
||||
* @return {!goog.async.Deferred} The deferred list of results from the inputs
|
||||
* if they all succeed, or the error result of the first input to fail.
|
||||
*/
|
||||
goog.async.DeferredList.gatherResults = function(list) {
|
||||
return new goog.async.DeferredList(list, false, true).
|
||||
addCallback(function(results) {
|
||||
var output = [];
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
output[i] = results[i][1];
|
||||
}
|
||||
return output;
|
||||
});
|
||||
};
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
Author: brenneman@google.com (Shawn Brenneman)
|
||||
-->
|
||||
<head>
|
||||
<title>Closure Unit Tests - goog.async.DeferredList</title>
|
||||
<script src="../../../../../closure/goog/base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.async.DeferredList');
|
||||
goog.require('goog.testing.jsunit');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var Deferred = goog.async.Deferred;
|
||||
var DeferredList = goog.async.DeferredList;
|
||||
|
||||
|
||||
// Re-throw (after a timeout) any errors not handled in an errback.
|
||||
Deferred.STRICT_ERRORS = true;
|
||||
|
||||
|
||||
/**
|
||||
* A list of unhandled errors.
|
||||
* @type {Array.<Error>}
|
||||
*/
|
||||
var storedErrors = [];
|
||||
|
||||
|
||||
/**
|
||||
* Adds a catch-all error handler to deferred objects. Unhandled errors that
|
||||
* reach the catch-all will be rethrown during tearDown.
|
||||
*
|
||||
* @param {...Deferred} var_args A list of deferred objects.
|
||||
*/
|
||||
function addCatchAll(var_args) {
|
||||
for (var i = 0, d; d = arguments[i]; i++) {
|
||||
d.addErrback(function(res) {
|
||||
storedErrors.push(res);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks storedErrors for unhandled errors. If found, the error is rethrown.
|
||||
*/
|
||||
function checkCatchAll() {
|
||||
var err = storedErrors.shift();
|
||||
goog.array.clear(storedErrors);
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function tearDown() {
|
||||
checkCatchAll();
|
||||
}
|
||||
|
||||
|
||||
function neverHappen(res) {
|
||||
fail('This should not happen');
|
||||
}
|
||||
|
||||
|
||||
function testNoInputs() {
|
||||
var count = 0;
|
||||
var d = new DeferredList([]);
|
||||
|
||||
d.addCallback(function(res) {
|
||||
assertArrayEquals([], res);
|
||||
count++;
|
||||
});
|
||||
addCatchAll(d);
|
||||
|
||||
assertEquals('An empty DeferredList should fire immediately with an empty ' +
|
||||
'list of results.',
|
||||
1, count);
|
||||
}
|
||||
|
||||
|
||||
function testNoInputsAndFireOnOneCallback() {
|
||||
var count = 0;
|
||||
var d = new DeferredList([], true);
|
||||
|
||||
d.addCallback(function(res) {
|
||||
assertArrayEquals([], res);
|
||||
count++;
|
||||
});
|
||||
addCatchAll(d);
|
||||
|
||||
assertEquals('An empty DeferredList with opt_fireOnOneCallback set should ' +
|
||||
'not fire unless callback is invoked explicitly.',
|
||||
0, count);
|
||||
|
||||
d.callback([]);
|
||||
assertEquals('Calling callback explicitly should still fire.', 1, count);
|
||||
}
|
||||
|
||||
|
||||
function testDeferredList() {
|
||||
var count = 0;
|
||||
var results;
|
||||
|
||||
var a = new Deferred();
|
||||
var b = new Deferred();
|
||||
var c = new Deferred();
|
||||
|
||||
var dl = new DeferredList([a, b, c]);
|
||||
|
||||
dl.addCallback(function(res) {
|
||||
assertEquals('Expected 3 Deferred results.', 3, res.length);
|
||||
|
||||
assertTrue('Deferred a should return success.', res[0][0]);
|
||||
assertFalse('Deferred b should return failure.', res[1][0]);
|
||||
assertTrue('Deferred c should return success.', res[2][0]);
|
||||
|
||||
assertEquals('Unexpected return value for a.', 'A', res[0][1]);
|
||||
assertEquals('Unexpected return value for c.', 'C', res[2][1]);
|
||||
|
||||
assertEquals('B', res[1][1]);
|
||||
|
||||
count++;
|
||||
});
|
||||
|
||||
addCatchAll(dl);
|
||||
|
||||
c.callback('C');
|
||||
assertEquals(0, count);
|
||||
|
||||
b.errback('B');
|
||||
assertEquals(0, count);
|
||||
|
||||
a.callback('A');
|
||||
|
||||
checkCatchAll();
|
||||
assertEquals('DeferredList should fire on last call or errback.', 1, count);
|
||||
}
|
||||
|
||||
|
||||
function testFireOnFirstCallback() {
|
||||
var a = new Deferred();
|
||||
var b = new Deferred();
|
||||
var c = new Deferred();
|
||||
|
||||
var dl = new DeferredList([a, b, c], true);
|
||||
|
||||
dl.addCallback(function(res) {
|
||||
assertEquals('Should be the deferred index in this mode.', 1, res[0]);
|
||||
assertEquals('B', res[1]);
|
||||
});
|
||||
dl.addErrback(neverHappen);
|
||||
|
||||
addCatchAll(dl);
|
||||
|
||||
a.errback('A');
|
||||
b.callback('B');
|
||||
|
||||
// Shouldn't cause any more callbacks on the DeferredList.
|
||||
c.callback('C');
|
||||
}
|
||||
|
||||
|
||||
function testFireOnFirstErrback() {
|
||||
var a = new Deferred();
|
||||
var b = new Deferred();
|
||||
var c = new Deferred();
|
||||
|
||||
var dl = new DeferredList([a, b, c], false, true);
|
||||
|
||||
dl.addCallback(neverHappen);
|
||||
dl.addErrback(function(res) {
|
||||
assertEquals('A', res);
|
||||
|
||||
// Return a non-error value to break out of the errback path.
|
||||
return null;
|
||||
});
|
||||
addCatchAll(dl);
|
||||
|
||||
b.callback('B');
|
||||
a.errback('A');
|
||||
|
||||
assertTrue(c.hasFired());
|
||||
c.addErrback(function(res) {
|
||||
assertTrue(
|
||||
'The DeferredList errback should have canceled all pending inputs.',
|
||||
res instanceof Deferred.CanceledError);
|
||||
return null;
|
||||
});
|
||||
addCatchAll(c);
|
||||
|
||||
// Shouldn't cause any more callbacks on the DeferredList.
|
||||
c.callback('C');
|
||||
}
|
||||
|
||||
|
||||
function testNoConsumeErrors() {
|
||||
var count = 0;
|
||||
|
||||
var a = new Deferred();
|
||||
var dl = new DeferredList([a]);
|
||||
|
||||
a.addErrback(function(res) {
|
||||
count++;
|
||||
return null;
|
||||
});
|
||||
|
||||
addCatchAll(a, dl);
|
||||
|
||||
a.errback('oh noes');
|
||||
assertEquals(1, count);
|
||||
}
|
||||
|
||||
|
||||
function testConsumeErrors() {
|
||||
var count = 0;
|
||||
|
||||
var a = new Deferred();
|
||||
var dl = new DeferredList([a], false, false, true);
|
||||
|
||||
a.addErrback(neverHappen);
|
||||
|
||||
addCatchAll(a, dl);
|
||||
|
||||
a.errback('oh noes');
|
||||
assertEquals(0, count);
|
||||
}
|
||||
|
||||
|
||||
function testNesting() {
|
||||
|
||||
function upperCase(res) {
|
||||
return res.toUpperCase();
|
||||
}
|
||||
|
||||
// Concatenates a list of callback or errback results into a single string.
|
||||
function combine(res) {
|
||||
return goog.array.map(res, function(result) {
|
||||
return result[1];
|
||||
}).join('');
|
||||
}
|
||||
|
||||
var a = new Deferred();
|
||||
var b = new Deferred();
|
||||
var c = new Deferred();
|
||||
var d = new Deferred();
|
||||
|
||||
a.addCallback(upperCase);
|
||||
b.addCallback(upperCase);
|
||||
c.addCallback(upperCase);
|
||||
d.addCallback(upperCase);
|
||||
|
||||
var dl1 = new DeferredList([a, b]);
|
||||
var dl2 = new DeferredList([c, d]);
|
||||
|
||||
dl1.addCallback(combine);
|
||||
dl2.addCallback(combine);
|
||||
|
||||
var dl3 = new DeferredList([dl1, dl2]);
|
||||
dl3.addCallback(combine);
|
||||
dl3.addCallback(function(res) {
|
||||
assertEquals('AbCd', res);
|
||||
});
|
||||
|
||||
addCatchAll(dl1, dl2, dl3);
|
||||
|
||||
a.callback('a');
|
||||
c.callback('c');
|
||||
b.errback('b');
|
||||
d.errback('d');
|
||||
}
|
||||
|
||||
|
||||
function testGatherResults() {
|
||||
var a = new Deferred();
|
||||
var b = new Deferred();
|
||||
var c = new Deferred();
|
||||
|
||||
var dl = DeferredList.gatherResults([a, b, c]);
|
||||
|
||||
dl.addCallback(function(res) {
|
||||
assertArrayEquals(['A', 'B', 'C'], res);
|
||||
});
|
||||
|
||||
addCatchAll(dl);
|
||||
|
||||
b.callback('B');
|
||||
a.callback('A');
|
||||
c.callback('C');
|
||||
}
|
||||
|
||||
|
||||
function testGatherResultsFailure() {
|
||||
var a = new Deferred();
|
||||
var b = new Deferred();
|
||||
var c = new Deferred();
|
||||
|
||||
var dl = DeferredList.gatherResults([a, b, c]);
|
||||
|
||||
var firedErrback = false;
|
||||
var firedCallback = false;
|
||||
dl.addCallback(function() {
|
||||
firedCallback = true;
|
||||
});
|
||||
dl.addErrback(function() {
|
||||
firedErrback = true;
|
||||
return null;
|
||||
});
|
||||
|
||||
addCatchAll(dl);
|
||||
|
||||
b.callback('B');
|
||||
a.callback('A');
|
||||
c.errback();
|
||||
|
||||
assertTrue('Errback should be called', firedErrback);
|
||||
assertFalse('Callback should not be called', firedCallback);
|
||||
}
|
||||
|
||||
|
||||
function testGatherResults_cancelCancelsChildren() {
|
||||
var canceled = [];
|
||||
var a = new Deferred(function() {
|
||||
canceled.push('a');
|
||||
});
|
||||
var b = new Deferred(function() {
|
||||
canceled.push('b');
|
||||
});
|
||||
var c = new Deferred(function() {
|
||||
canceled.push('c');
|
||||
});
|
||||
|
||||
var dl = new DeferredList([a, b, c]);
|
||||
|
||||
var firedErrback = false;
|
||||
var firedCallback = false;
|
||||
dl.addCallback(function() {
|
||||
firedCallback = true;
|
||||
});
|
||||
dl.addErrback(function() {
|
||||
firedErrback = true;
|
||||
return null;
|
||||
});
|
||||
|
||||
addCatchAll(dl);
|
||||
|
||||
b.callback('b');
|
||||
dl.cancel();
|
||||
|
||||
assertTrue('Errback should be called', firedErrback);
|
||||
assertFalse('Callback should not be called', firedCallback);
|
||||
assertArrayEquals(['a', 'c'], canceled);
|
||||
}
|
||||
|
||||
|
||||
function testErrorCancelsPendingChildrenWhenFireOnFirstError() {
|
||||
var canceled = [];
|
||||
var a = new Deferred(function() {
|
||||
canceled.push('a');
|
||||
});
|
||||
var b = new Deferred(function() {
|
||||
canceled.push('b');
|
||||
});
|
||||
var c = new Deferred(function() {
|
||||
canceled.push('c');
|
||||
});
|
||||
|
||||
var dl = new DeferredList([a, b, c], false, true);
|
||||
|
||||
var firedErrback = false;
|
||||
var firedCallback = false;
|
||||
dl.addCallback(function() {
|
||||
firedCallback = true;
|
||||
});
|
||||
dl.addErrback(function() {
|
||||
firedErrback = true;
|
||||
return null;
|
||||
});
|
||||
|
||||
addCatchAll(dl);
|
||||
|
||||
a.callback('a')
|
||||
b.errback();
|
||||
|
||||
assertTrue('Errback should be called', firedErrback);
|
||||
assertFalse('Callback should not be called', firedCallback);
|
||||
assertArrayEquals('Only C should be canceled since A and B fired.',
|
||||
['c'], canceled);
|
||||
}
|
||||
|
||||
|
||||
function testErrorDoesNotCancelPendingChildrenForVanillaLists() {
|
||||
var canceled = [];
|
||||
var a = new Deferred(function() {
|
||||
canceled.push('a');
|
||||
});
|
||||
var b = new Deferred(function() {
|
||||
canceled.push('b');
|
||||
});
|
||||
var c = new Deferred(function() {
|
||||
canceled.push('c');
|
||||
});
|
||||
|
||||
var dl = new DeferredList([a, b, c]);
|
||||
|
||||
var firedErrback = false;
|
||||
var firedCallback = false;
|
||||
dl.addCallback(function() {
|
||||
firedCallback = true;
|
||||
});
|
||||
dl.addErrback(function() {
|
||||
firedErrback = true;
|
||||
return null;
|
||||
});
|
||||
|
||||
addCatchAll(dl);
|
||||
|
||||
a.callback('a')
|
||||
b.errback();
|
||||
c.callback('c')
|
||||
|
||||
assertFalse('Errback should not be called', firedErrback);
|
||||
assertTrue('Callback should be called', firedCallback);
|
||||
assertArrayEquals('No cancellations', [], canceled);
|
||||
}
|
||||
|
||||
|
||||
function testInputDeferredsStillUsable() {
|
||||
var increment = function(res) {
|
||||
return res + 1;
|
||||
};
|
||||
var incrementErrback = function(res) {
|
||||
throw res + 1;
|
||||
};
|
||||
|
||||
var aComplete = false;
|
||||
var bComplete = false;
|
||||
var hadListCallback = false;
|
||||
|
||||
var a = new Deferred().addCallback(increment);
|
||||
var b = new Deferred().addErrback(incrementErrback);
|
||||
var c = new Deferred();
|
||||
|
||||
var dl = new DeferredList([a, b, c]);
|
||||
|
||||
a.callback(0);
|
||||
a.addCallback(increment);
|
||||
a.addCallback(function(res) {
|
||||
aComplete = true;
|
||||
assertEquals(
|
||||
'The "a" Deferred should have had two increment callbacks.',
|
||||
2, res);
|
||||
});
|
||||
assertTrue('The "a" deferred should complete before the list.', aComplete);
|
||||
|
||||
b.errback(0);
|
||||
b.addErrback(incrementErrback);
|
||||
b.addErrback(function(res) {
|
||||
bComplete = true;
|
||||
assertEquals(
|
||||
'The "b" Deferred should have had two increment errbacks.',
|
||||
2, res);
|
||||
});
|
||||
assertTrue('The "b" deferred should complete before the list.', bComplete);
|
||||
|
||||
assertFalse('The list should not fire until every input has.', dl.hasFired());
|
||||
c.callback();
|
||||
assertTrue(dl.hasFired());
|
||||
|
||||
assertFalse(hadListCallback);
|
||||
dl.addCallback(function(results) {
|
||||
hadListCallback = true;
|
||||
|
||||
var aResult = results[0];
|
||||
var bResult = results[1];
|
||||
var cResult = results[2];
|
||||
|
||||
assertTrue(aResult[0]);
|
||||
assertEquals(
|
||||
'Should see the result from before the second callback was added.',
|
||||
1, aResult[1]);
|
||||
|
||||
assertFalse(bResult[0]);
|
||||
assertEquals(
|
||||
'Should see the result from before the second errback was added.',
|
||||
1, aResult[1]);
|
||||
|
||||
assertTrue(cResult[0]);
|
||||
});
|
||||
assertTrue(hadListCallback);
|
||||
|
||||
addCatchAll(dl);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user