Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/mochikit/async/deferred.js
4571 views
1
// Copyright 2007 Bob Ippolito. All Rights Reserved.
2
// Modifications Copyright 2009 The Closure Library Authors. All Rights
3
// Reserved.
4
5
/**
6
* @license Portions of this code are from MochiKit, received by
7
* The Closure Authors under the MIT license. All other code is Copyright
8
* 2005-2009 The Closure Authors. All Rights Reserved.
9
*/
10
11
/**
12
* @fileoverview Classes for tracking asynchronous operations and handling the
13
* results. The Deferred object here is patterned after the Deferred object in
14
* the Twisted python networking framework.
15
*
16
* See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
17
*
18
* Based on the Dojo code which in turn is based on the MochiKit code.
19
*
20
* @author [email protected] (Erik Arvidsson)
21
* @author [email protected] (Shawn Brenneman)
22
*/
23
24
goog.provide('goog.async.Deferred');
25
goog.provide('goog.async.Deferred.AlreadyCalledError');
26
goog.provide('goog.async.Deferred.CanceledError');
27
28
goog.require('goog.Promise');
29
goog.require('goog.Thenable');
30
goog.require('goog.array');
31
goog.require('goog.asserts');
32
goog.require('goog.debug.Error');
33
34
35
36
/**
37
* A Deferred represents the result of an asynchronous operation. A Deferred
38
* instance has no result when it is created, and is "fired" (given an initial
39
* result) by calling `callback` or `errback`.
40
*
41
* Once fired, the result is passed through a sequence of callback functions
42
* registered with `addCallback` or `addErrback`. The functions may
43
* mutate the result before it is passed to the next function in the sequence.
44
*
45
* Callbacks and errbacks may be added at any time, including after the Deferred
46
* has been "fired". If there are no pending actions in the execution sequence
47
* of a fired Deferred, any new callback functions will be called with the last
48
* computed result. Adding a callback function is the only way to access the
49
* result of the Deferred.
50
*
51
* If a Deferred operation is canceled, an optional user-provided cancellation
52
* function is invoked which may perform any special cleanup, followed by firing
53
* the Deferred's errback sequence with a `CanceledError`. If the
54
* Deferred has already fired, cancellation is ignored.
55
*
56
* Deferreds may be templated to a specific type they produce using generics
57
* with syntax such as:
58
*
59
* /** @type {goog.async.Deferred<string>} *\
60
* var d = new goog.async.Deferred();
61
* // Compiler can infer that foo is a string.
62
* d.addCallback(function(foo) {...});
63
* d.callback('string'); // Checked to be passed a string
64
*
65
* Since deferreds are often used to produce different values across a chain,
66
* the type information is not propagated across chains, but rather only
67
* associated with specifically cast objects.
68
*
69
* @param {Function=} opt_onCancelFunction A function that will be called if the
70
* Deferred is canceled. If provided, this function runs before the
71
* Deferred is fired with a `CanceledError`.
72
* @param {Object=} opt_defaultScope The default object context to call
73
* callbacks and errbacks in.
74
* @constructor
75
* @implements {goog.Thenable<VALUE>}
76
* @template VALUE
77
*/
78
goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
79
/**
80
* Entries in the sequence are arrays containing a callback, an errback, and
81
* an optional scope. The callback or errback in an entry may be null.
82
* @type {!Array<!Array>}
83
* @private
84
*/
85
this.sequence_ = [];
86
87
/**
88
* Optional function that will be called if the Deferred is canceled.
89
* @type {Function|undefined}
90
* @private
91
*/
92
this.onCancelFunction_ = opt_onCancelFunction;
93
94
/**
95
* The default scope to execute callbacks and errbacks in.
96
* @type {Object}
97
* @private
98
*/
99
this.defaultScope_ = opt_defaultScope || null;
100
101
/**
102
* Whether the Deferred has been fired.
103
* @type {boolean}
104
* @private
105
*/
106
this.fired_ = false;
107
108
/**
109
* Whether the last result in the execution sequence was an error.
110
* @type {boolean}
111
* @private
112
*/
113
this.hadError_ = false;
114
115
/**
116
* The current Deferred result, updated as callbacks and errbacks are
117
* executed.
118
* @type {*}
119
* @private
120
*/
121
this.result_ = undefined;
122
123
/**
124
* Whether the Deferred is blocked waiting on another Deferred to fire. If a
125
* callback or errback returns a Deferred as a result, the execution sequence
126
* is blocked until that Deferred result becomes available.
127
* @type {boolean}
128
* @private
129
*/
130
this.blocked_ = false;
131
132
/**
133
* Whether this Deferred is blocking execution of another Deferred. If this
134
* instance was returned as a result in another Deferred's execution
135
* sequence,that other Deferred becomes blocked until this instance's
136
* execution sequence completes. No additional callbacks may be added to a
137
* Deferred once it is blocking another instance.
138
* @type {boolean}
139
* @private
140
*/
141
this.blocking_ = false;
142
143
/**
144
* Whether the Deferred has been canceled without having a custom cancel
145
* function.
146
* @type {boolean}
147
* @private
148
*/
149
this.silentlyCanceled_ = false;
150
151
/**
152
* If an error is thrown during Deferred execution with no errback to catch
153
* it, the error is rethrown after a timeout. Reporting the error after a
154
* timeout allows execution to continue in the calling context (empty when
155
* no error is scheduled).
156
* @type {number}
157
* @private
158
*/
159
this.unhandledErrorId_ = 0;
160
161
/**
162
* If this Deferred was created by branch(), this will be the "parent"
163
* Deferred.
164
* @type {?goog.async.Deferred}
165
* @private
166
*/
167
this.parent_ = null;
168
169
/**
170
* The number of Deferred objects that have been branched off this one. This
171
* will be decremented whenever a branch is fired or canceled.
172
* @type {number}
173
* @private
174
*/
175
this.branches_ = 0;
176
177
if (goog.async.Deferred.LONG_STACK_TRACES) {
178
/**
179
* Holds the stack trace at time of deferred creation if the JS engine
180
* provides the Error.captureStackTrace API.
181
* @private {?string}
182
*/
183
this.constructorStack_ = null;
184
if (Error.captureStackTrace) {
185
var target = { stack: '' };
186
Error.captureStackTrace(target, goog.async.Deferred);
187
// Check if Error.captureStackTrace worked. It fails in gjstest.
188
if (typeof target.stack == 'string') {
189
// Remove first line and force stringify to prevent memory leak due to
190
// holding on to actual stack frames.
191
this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, '');
192
}
193
}
194
}
195
};
196
197
198
/**
199
* @define {boolean} Whether unhandled errors should always get rethrown to the
200
* global scope. Defaults to false.
201
*/
202
goog.async.Deferred.STRICT_ERRORS =
203
goog.define('goog.async.Deferred.STRICT_ERRORS', false);
204
205
206
/**
207
* @define {boolean} Whether to attempt to make stack traces long. Defaults to
208
* false.
209
*/
210
goog.async.Deferred.LONG_STACK_TRACES =
211
goog.define('goog.async.Deferred.LONG_STACK_TRACES', false);
212
213
214
/**
215
* Cancels a Deferred that has not yet been fired, or is blocked on another
216
* deferred operation. If this Deferred is waiting for a blocking Deferred to
217
* fire, the blocking Deferred will also be canceled.
218
*
219
* If this Deferred was created by calling branch() on a parent Deferred with
220
* opt_propagateCancel set to true, the parent may also be canceled. If
221
* opt_deepCancel is set, cancel() will be called on the parent (as well as any
222
* other ancestors if the parent is also a branch). If one or more branches were
223
* created with opt_propagateCancel set to true, the parent will be canceled if
224
* cancel() is called on all of those branches.
225
*
226
* @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
227
* if cancel() hasn't been called on some of the parent's branches. Has no
228
* effect on a branch without opt_propagateCancel set to true.
229
*/
230
goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
231
if (!this.hasFired()) {
232
if (this.parent_) {
233
// Get rid of the parent reference before potentially running the parent's
234
// canceler function to ensure that this cancellation isn't
235
// double-counted.
236
var parent = this.parent_;
237
delete this.parent_;
238
if (opt_deepCancel) {
239
parent.cancel(opt_deepCancel);
240
} else {
241
parent.branchCancel_();
242
}
243
}
244
245
if (this.onCancelFunction_) {
246
// Call in user-specified scope.
247
this.onCancelFunction_.call(this.defaultScope_, this);
248
} else {
249
this.silentlyCanceled_ = true;
250
}
251
if (!this.hasFired()) {
252
this.errback(new goog.async.Deferred.CanceledError(this));
253
}
254
} else if (this.result_ instanceof goog.async.Deferred) {
255
this.result_.cancel();
256
}
257
};
258
259
260
/**
261
* Handle a single branch being canceled. Once all branches are canceled, this
262
* Deferred will be canceled as well.
263
*
264
* @private
265
*/
266
goog.async.Deferred.prototype.branchCancel_ = function() {
267
this.branches_--;
268
if (this.branches_ <= 0) {
269
this.cancel();
270
}
271
};
272
273
274
/**
275
* Called after a blocking Deferred fires. Unblocks this Deferred and resumes
276
* its execution sequence.
277
*
278
* @param {boolean} isSuccess Whether the result is a success or an error.
279
* @param {*} res The result of the blocking Deferred.
280
* @private
281
*/
282
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
283
this.blocked_ = false;
284
this.updateResult_(isSuccess, res);
285
};
286
287
288
/**
289
* Updates the current result based on the success or failure of the last action
290
* in the execution sequence.
291
*
292
* @param {boolean} isSuccess Whether the new result is a success or an error.
293
* @param {*} res The result.
294
* @private
295
*/
296
goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
297
this.fired_ = true;
298
this.result_ = res;
299
this.hadError_ = !isSuccess;
300
this.fire_();
301
};
302
303
304
/**
305
* Verifies that the Deferred has not yet been fired.
306
*
307
* @private
308
* @throws {Error} If this has already been fired.
309
*/
310
goog.async.Deferred.prototype.check_ = function() {
311
if (this.hasFired()) {
312
if (!this.silentlyCanceled_) {
313
throw new goog.async.Deferred.AlreadyCalledError(this);
314
}
315
this.silentlyCanceled_ = false;
316
}
317
};
318
319
320
/**
321
* Fire the execution sequence for this Deferred by passing the starting result
322
* to the first registered callback.
323
* @param {VALUE=} opt_result The starting result.
324
*/
325
goog.async.Deferred.prototype.callback = function(opt_result) {
326
this.check_();
327
this.assertNotDeferred_(opt_result);
328
this.updateResult_(true /* isSuccess */, opt_result);
329
};
330
331
332
/**
333
* Fire the execution sequence for this Deferred by passing the starting error
334
* result to the first registered errback.
335
* @param {*=} opt_result The starting error.
336
*/
337
goog.async.Deferred.prototype.errback = function(opt_result) {
338
this.check_();
339
this.assertNotDeferred_(opt_result);
340
this.makeStackTraceLong_(opt_result);
341
this.updateResult_(false /* isSuccess */, opt_result);
342
};
343
344
345
/**
346
* Attempt to make the error's stack trace be long in that it contains the
347
* stack trace from the point where the deferred was created on top of the
348
* current stack trace to give additional context.
349
* @param {*} error
350
* @private
351
*/
352
goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
353
if (!goog.async.Deferred.LONG_STACK_TRACES) {
354
return;
355
}
356
if (this.constructorStack_ && goog.isObject(error) && error.stack &&
357
// Stack looks like it was system generated. See
358
// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
359
(/^[^\n]+(\n [^\n]+)+/).test(error.stack)) {
360
error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
361
this.constructorStack_;
362
}
363
};
364
365
366
/**
367
* Asserts that an object is not a Deferred.
368
* @param {*} obj The object to test.
369
* @throws {Error} Throws an exception if the object is a Deferred.
370
* @private
371
*/
372
goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
373
goog.asserts.assert(
374
!(obj instanceof goog.async.Deferred),
375
'An execution sequence may not be initiated with a blocking Deferred.');
376
};
377
378
379
/**
380
* Register a callback function to be called with a successful result. If no
381
* value is returned by the callback function, the result value is unchanged. If
382
* a new value is returned, it becomes the Deferred result and will be passed to
383
* the next callback in the execution sequence.
384
*
385
* If the function throws an error, the error becomes the new result and will be
386
* passed to the next errback in the execution chain.
387
*
388
* If the function returns a Deferred, the execution sequence will be blocked
389
* until that Deferred fires. Its result will be passed to the next callback (or
390
* errback if it is an error result) in this Deferred's execution sequence.
391
*
392
* @param {function(this:T,VALUE):?} cb The function to be called with a
393
* successful result.
394
* @param {T=} opt_scope An optional scope to call the callback in.
395
* @return {!goog.async.Deferred} This Deferred.
396
* @template T
397
*/
398
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
399
return this.addCallbacks(cb, null, opt_scope);
400
};
401
402
403
/**
404
* Register a callback function to be called with an error result. If no value
405
* is returned by the function, the error result is unchanged. If a new error
406
* value is returned or thrown, that error becomes the Deferred result and will
407
* be passed to the next errback in the execution sequence.
408
*
409
* If the errback function handles the error by returning a non-error value,
410
* that result will be passed to the next normal callback in the sequence.
411
*
412
* If the function returns a Deferred, the execution sequence will be blocked
413
* until that Deferred fires. Its result will be passed to the next callback (or
414
* errback if it is an error result) in this Deferred's execution sequence.
415
*
416
* @param {function(this:T,?):?} eb The function to be called on an
417
* unsuccessful result.
418
* @param {T=} opt_scope An optional scope to call the errback in.
419
* @return {!goog.async.Deferred<VALUE>} This Deferred.
420
* @template T
421
*/
422
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
423
return this.addCallbacks(null, eb, opt_scope);
424
};
425
426
427
/**
428
* Registers one function as both a callback and errback.
429
*
430
* @param {function(this:T,?):?} f The function to be called on any result.
431
* @param {T=} opt_scope An optional scope to call the function in.
432
* @return {!goog.async.Deferred} This Deferred.
433
* @template T
434
*/
435
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
436
return this.addCallbacks(f, f, opt_scope);
437
};
438
439
440
/**
441
* Like addBoth, but propagates uncaught exceptions in the errback.
442
*
443
* @param {function(this:T,?):?} f The function to be called on any result.
444
* @param {T=} opt_scope An optional scope to call the function in.
445
* @return {!goog.async.Deferred<VALUE>} This Deferred.
446
* @template T
447
*/
448
goog.async.Deferred.prototype.addFinally = function(f, opt_scope) {
449
return this.addCallbacks(f, function(err) {
450
var result = f.call(/** @type {?} */ (this), err);
451
if (result === undefined) {
452
throw err;
453
}
454
return result;
455
}, opt_scope);
456
};
457
458
459
/**
460
* Registers a callback function and an errback function at the same position
461
* in the execution sequence. Only one of these functions will execute,
462
* depending on the error state during the execution sequence.
463
*
464
* NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
465
* the callback is invoked, the errback will be skipped, and vice versa.
466
*
467
* @param {?(function(this:T,VALUE):?)} cb The function to be called on a
468
* successful result.
469
* @param {?(function(this:T,?):?)} eb The function to be called on an
470
* unsuccessful result.
471
* @param {T=} opt_scope An optional scope to call the functions in.
472
* @return {!goog.async.Deferred} This Deferred.
473
* @template T
474
*/
475
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
476
goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
477
this.sequence_.push([cb, eb, opt_scope]);
478
if (this.hasFired()) {
479
this.fire_();
480
}
481
return this;
482
};
483
484
485
/**
486
* Implements {@see goog.Thenable} for seamless integration with
487
* {@see goog.Promise}.
488
* Deferred results are mutable and may represent multiple values over
489
* their lifetime. Calling `then` on a Deferred returns a Promise
490
* with the result of the Deferred at that point in its callback chain.
491
* Note that if the Deferred result is never mutated, and only
492
* `then` calls are made, the Deferred will behave like a Promise.
493
*
494
* @override
495
*/
496
goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected,
497
opt_context) {
498
var resolve, reject;
499
var promise = new goog.Promise(function(res, rej) {
500
// Copying resolvers to outer scope, so that they are available when the
501
// deferred callback fires (which may be synchronous).
502
resolve = res;
503
reject = rej;
504
});
505
this.addCallbacks(resolve, function(reason) {
506
if (reason instanceof goog.async.Deferred.CanceledError) {
507
promise.cancel();
508
} else {
509
reject(reason);
510
}
511
});
512
return promise.then(opt_onFulfilled, opt_onRejected, opt_context);
513
};
514
goog.Thenable.addImplementation(goog.async.Deferred);
515
516
517
/**
518
* Links another Deferred to the end of this Deferred's execution sequence. The
519
* result of this execution sequence will be passed as the starting result for
520
* the chained Deferred, invoking either its first callback or errback.
521
*
522
* @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
523
* @return {!goog.async.Deferred} This Deferred.
524
*/
525
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
526
this.addCallbacks(
527
otherDeferred.callback, otherDeferred.errback, otherDeferred);
528
return this;
529
};
530
531
532
/**
533
* Makes this Deferred wait for another Deferred's execution sequence to
534
* complete before continuing.
535
*
536
* This is equivalent to adding a callback that returns `otherDeferred`,
537
* but doesn't prevent additional callbacks from being added to
538
* `otherDeferred`.
539
*
540
* @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred
541
* to wait for.
542
* @return {!goog.async.Deferred} This Deferred.
543
*/
544
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
545
if (!(otherDeferred instanceof goog.async.Deferred)) {
546
// The Thenable case.
547
return this.addCallback(function() {
548
return otherDeferred;
549
});
550
}
551
return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
552
};
553
554
555
/**
556
* Creates a branch off this Deferred's execution sequence, and returns it as a
557
* new Deferred. The branched Deferred's starting result will be shared with the
558
* parent at the point of the branch, even if further callbacks are added to the
559
* parent.
560
*
561
* All branches at the same stage in the execution sequence will receive the
562
* same starting value.
563
*
564
* @param {boolean=} opt_propagateCancel If cancel() is called on every child
565
* branch created with opt_propagateCancel, the parent will be canceled as
566
* well.
567
* @return {!goog.async.Deferred<VALUE>} A Deferred that will be started with
568
* the computed result from this stage in the execution sequence.
569
*/
570
goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
571
var d = new goog.async.Deferred();
572
this.chainDeferred(d);
573
if (opt_propagateCancel) {
574
d.parent_ = this;
575
this.branches_++;
576
}
577
return d;
578
};
579
580
581
/**
582
* @return {boolean} Whether the execution sequence has been started on this
583
* Deferred by invoking `callback` or `errback`.
584
*/
585
goog.async.Deferred.prototype.hasFired = function() {
586
return this.fired_;
587
};
588
589
590
/**
591
* @param {*} res The latest result in the execution sequence.
592
* @return {boolean} Whether the current result is an error that should cause
593
* the next errback to fire. May be overridden by subclasses to handle
594
* special error types.
595
* @protected
596
*/
597
goog.async.Deferred.prototype.isError = function(res) {
598
return res instanceof Error;
599
};
600
601
602
/**
603
* @return {boolean} Whether an errback exists in the remaining sequence.
604
* @private
605
*/
606
goog.async.Deferred.prototype.hasErrback_ = function() {
607
return goog.array.some(this.sequence_, function(sequenceRow) {
608
// The errback is the second element in the array.
609
return goog.isFunction(sequenceRow[1]);
610
});
611
};
612
613
614
/**
615
* Exhausts the execution sequence while a result is available. The result may
616
* be modified by callbacks or errbacks, and execution will block if the
617
* returned result is an incomplete Deferred.
618
*
619
* @private
620
*/
621
goog.async.Deferred.prototype.fire_ = function() {
622
if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) {
623
// It is possible to add errbacks after the Deferred has fired. If a new
624
// errback is added immediately after the Deferred encountered an unhandled
625
// error, but before that error is rethrown, the error is unscheduled.
626
goog.async.Deferred.unscheduleError_(this.unhandledErrorId_);
627
this.unhandledErrorId_ = 0;
628
}
629
630
if (this.parent_) {
631
this.parent_.branches_--;
632
delete this.parent_;
633
}
634
635
var res = this.result_;
636
var unhandledException = false;
637
var isNewlyBlocked = false;
638
639
while (this.sequence_.length && !this.blocked_) {
640
var sequenceEntry = this.sequence_.shift();
641
642
var callback = sequenceEntry[0];
643
var errback = sequenceEntry[1];
644
var scope = sequenceEntry[2];
645
646
var f = this.hadError_ ? errback : callback;
647
if (f) {
648
649
try {
650
var ret = f.call(scope || this.defaultScope_, res);
651
652
// If no result, then use previous result.
653
if (ret !== undefined) {
654
// Bubble up the error as long as the return value hasn't changed.
655
this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
656
this.result_ = res = ret;
657
}
658
659
if (goog.Thenable.isImplementedBy(res) ||
660
(typeof goog.global['Promise'] === 'function' &&
661
res instanceof goog.global['Promise'])) {
662
isNewlyBlocked = true;
663
this.blocked_ = true;
664
}
665
666
} catch (ex) {
667
res = ex;
668
this.hadError_ = true;
669
this.makeStackTraceLong_(res);
670
671
if (!this.hasErrback_()) {
672
// If an error is thrown with no additional errbacks in the queue,
673
// prepare to rethrow the error.
674
unhandledException = true;
675
}
676
}
677
}
678
}
679
680
this.result_ = res;
681
682
if (isNewlyBlocked) {
683
var onCallback = goog.bind(this.continue_, this, true /* isSuccess */);
684
var onErrback = goog.bind(this.continue_, this, false /* isSuccess */);
685
686
if (res instanceof goog.async.Deferred) {
687
res.addCallbacks(onCallback, onErrback);
688
res.blocking_ = true;
689
} else {
690
/** @type {!IThenable} */ (res).then(onCallback, onErrback);
691
}
692
} else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
693
!(res instanceof goog.async.Deferred.CanceledError)) {
694
this.hadError_ = true;
695
unhandledException = true;
696
}
697
698
if (unhandledException) {
699
// Rethrow the unhandled error after a timeout. Execution will continue, but
700
// the error will be seen by global handlers and the user. The throw will
701
// be canceled if another errback is appended before the timeout executes.
702
// The error's original stack trace is preserved where available.
703
this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res);
704
}
705
};
706
707
708
/**
709
* Creates a Deferred that has an initial result.
710
*
711
* @param {*=} opt_result The result.
712
* @return {!goog.async.Deferred} The new Deferred.
713
*/
714
goog.async.Deferred.succeed = function(opt_result) {
715
var d = new goog.async.Deferred();
716
d.callback(opt_result);
717
return d;
718
};
719
720
721
/**
722
* Creates a Deferred that fires when the given promise resolves.
723
* Use only during migration to Promises.
724
*
725
* Note: If the promise resolves to a thenable value (which is not allowed by
726
* conforming promise implementations), then the deferred may behave
727
* unexpectedly as it tries to wait on it. This should not be a risk when using
728
* goog.Promise, goog.async.Deferred, or native Promise objects.
729
*
730
* @param {!IThenable<T>} promise
731
* @return {!goog.async.Deferred<T>} The new Deferred.
732
* @template T
733
*/
734
goog.async.Deferred.fromPromise = function(promise) {
735
var d = new goog.async.Deferred();
736
promise.then(
737
function(value) {
738
d.callback(value);
739
},
740
function(error) {
741
d.errback(error);
742
});
743
return d;
744
};
745
746
747
/**
748
* Creates a Deferred that has an initial error result.
749
*
750
* @param {*} res The error result.
751
* @return {!goog.async.Deferred} The new Deferred.
752
*/
753
goog.async.Deferred.fail = function(res) {
754
var d = new goog.async.Deferred();
755
d.errback(res);
756
return d;
757
};
758
759
760
/**
761
* Creates a Deferred that has already been canceled.
762
*
763
* @return {!goog.async.Deferred} The new Deferred.
764
*/
765
goog.async.Deferred.canceled = function() {
766
var d = new goog.async.Deferred();
767
d.cancel();
768
return d;
769
};
770
771
772
/**
773
* Normalizes values that may or may not be Deferreds.
774
*
775
* If the input value is a Deferred, the Deferred is branched (so the original
776
* execution sequence is not modified) and the input callback added to the new
777
* branch. The branch is returned to the caller.
778
*
779
* If the input value is not a Deferred, the callback will be executed
780
* immediately and an already firing Deferred will be returned to the caller.
781
*
782
* In the following (contrived) example, if <code>isImmediate</code> is true
783
* then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
784
*
785
* <pre>
786
* var value;
787
* if (isImmediate) {
788
* value = 3;
789
* } else {
790
* value = new goog.async.Deferred();
791
* setTimeout(function() { value.callback(6); }, 2000);
792
* }
793
*
794
* var d = goog.async.Deferred.when(value, alert);
795
* </pre>
796
*
797
* @param {*} value Deferred or normal value to pass to the callback.
798
* @param {function(this:T, ?):?} callback The callback to execute.
799
* @param {T=} opt_scope An optional scope to call the callback in.
800
* @return {!goog.async.Deferred} A new Deferred that will call the input
801
* callback with the input value.
802
* @template T
803
*/
804
goog.async.Deferred.when = function(value, callback, opt_scope) {
805
if (value instanceof goog.async.Deferred) {
806
return value.branch(true).addCallback(callback, opt_scope);
807
} else {
808
return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
809
}
810
};
811
812
813
814
/**
815
* An error sub class that is used when a Deferred has already been called.
816
* @param {!goog.async.Deferred} deferred The Deferred.
817
*
818
* @constructor
819
* @extends {goog.debug.Error}
820
*/
821
goog.async.Deferred.AlreadyCalledError = function(deferred) {
822
goog.debug.Error.call(this);
823
824
/**
825
* The Deferred that raised this error.
826
* @type {goog.async.Deferred}
827
*/
828
this.deferred = deferred;
829
};
830
goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
831
832
833
/** @override */
834
goog.async.Deferred.AlreadyCalledError.prototype.message =
835
'Deferred has already fired';
836
837
838
/** @override */
839
goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
840
841
842
843
/**
844
* An error sub class that is used when a Deferred is canceled.
845
*
846
* @param {!goog.async.Deferred} deferred The Deferred object.
847
* @constructor
848
* @extends {goog.debug.Error}
849
*/
850
goog.async.Deferred.CanceledError = function(deferred) {
851
goog.debug.Error.call(this);
852
853
/**
854
* The Deferred that raised this error.
855
* @type {goog.async.Deferred}
856
*/
857
this.deferred = deferred;
858
};
859
goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
860
861
862
/** @override */
863
goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
864
865
866
/** @override */
867
goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';
868
869
870
871
/**
872
* Wrapper around errors that are scheduled to be thrown by failing deferreds
873
* after a timeout.
874
*
875
* @param {*} error Error from a failing deferred.
876
* @constructor
877
* @final
878
* @private
879
* @struct
880
*/
881
goog.async.Deferred.Error_ = function(error) {
882
/** @const @private {number} */
883
this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0);
884
885
/** @const @private {*} */
886
this.error_ = error;
887
};
888
889
890
/**
891
* Actually throws the error and removes it from the list of pending
892
* deferred errors.
893
*/
894
goog.async.Deferred.Error_.prototype.throwError = function() {
895
goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_],
896
'Cannot throw an error that is not scheduled.');
897
delete goog.async.Deferred.errorMap_[this.id_];
898
throw this.error_;
899
};
900
901
902
/**
903
* Resets the error throw timer.
904
*/
905
goog.async.Deferred.Error_.prototype.resetTimer = function() {
906
goog.global.clearTimeout(this.id_);
907
};
908
909
910
/**
911
* Map of unhandled errors scheduled to be rethrown in a future timestep.
912
* @private {!Object<(number|string), goog.async.Deferred.Error_>}
913
*/
914
goog.async.Deferred.errorMap_ = {};
915
916
917
/**
918
* Schedules an error to be thrown after a delay.
919
* @param {*} error Error from a failing deferred.
920
* @return {number} Id of the error.
921
* @private
922
*/
923
goog.async.Deferred.scheduleError_ = function(error) {
924
var deferredError = new goog.async.Deferred.Error_(error);
925
goog.async.Deferred.errorMap_[deferredError.id_] = deferredError;
926
return deferredError.id_;
927
};
928
929
930
/**
931
* Unschedules an error from being thrown.
932
* @param {number} id Id of the deferred error to unschedule.
933
* @private
934
*/
935
goog.async.Deferred.unscheduleError_ = function(id) {
936
var error = goog.async.Deferred.errorMap_[id];
937
if (error) {
938
error.resetTimer();
939
delete goog.async.Deferred.errorMap_[id];
940
}
941
};
942
943
944
/**
945
* Asserts that there are no pending deferred errors. If there are any
946
* scheduled errors, one will be thrown immediately to make this function fail.
947
*/
948
goog.async.Deferred.assertNoErrors = function() {
949
var map = goog.async.Deferred.errorMap_;
950
for (var key in map) {
951
var error = map[key];
952
error.resetTimer();
953
error.throwError();
954
}
955
};
956
957