This repository has been archived by the owner on Apr 16, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathpromise.js
591 lines (501 loc) · 19.4 KB
/
promise.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
/*!
Copyright 2013 Yahoo! Inc. All rights reserved.
Licensed under the BSD License.
http://yuilibrary.com/license/
*/
/*jslint expr: true */
/*global define */
(function (global, factory) {
var built = factory();
/* istanbul ignore else */
if (typeof module === 'object' && module) {
module.exports = built;
}
/* istanbul ignore next */
if (typeof define === 'function' && define.amd) {
define(factory);
}
global.PromisePolyfill = built;
global.Promise || (global.Promise = built);
}(typeof global !== 'undefined' ? global : /* istanbul ignore next */ this, function () {
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
function assign(obj, props) {
for (var prop in props) {
/* istanbul ignore else */
if (props.hasOwnProperty(prop)) {
obj[prop] = props[prop];
}
}
}
/**
A promise represents a value that may not yet be available. Promises allow
you to chain asynchronous operations, write synchronous looking code and
handle errors throughout the process.
This constructor takes a function as a parameter where you can insert the logic
that fulfills or rejects this promise. The fulfillment value and the rejection
reason can be any JavaScript value. It's encouraged that rejection reasons be
error objects
<pre><code>
var fulfilled = new Promise(function (resolve) {
resolve('I am a fulfilled promise');
});
var rejected = new Promise(function (resolve, reject) {
reject(new Error('I am a rejected promise'));
});
</code></pre>
@class Promise
@constructor
@param {Function} fn A function where to insert the logic that resolves this
promise. Receives `resolve` and `reject` functions as parameters.
This function is called synchronously.
**/
function Promise(fn) {
if (!(this instanceof Promise)) {
throw new TypeError(this + 'is not a promise');
}
if (typeof fn !== 'function') {
throw new TypeError('Promise resolver ' + fn + ' is not a function');
}
var resolver = new Resolver();
/**
A reference to the resolver object that handles this promise
@property _resolver
@type Object
@private
*/
this._resolver = resolver;
try {
fn(function (value) {
resolver.resolve(value);
}, function (reason) {
resolver.reject(reason);
});
} catch (e) {
resolver.reject(e);
}
}
assign(Promise.prototype, {
/**
Schedule execution of a callback to either or both of "fulfill" and
"reject" resolutions for this promise. The callbacks are wrapped in a new
promise and that promise is returned. This allows operation chaining ala
`functionA().then(functionB).then(functionC)` where `functionA` returns
a promise, and `functionB` and `functionC` _may_ return promises.
Asynchronicity of the callbacks is guaranteed.
@method then
@param {Function} [callback] function to execute if the promise
resolves successfully
@param {Function} [errback] function to execute if the promise
resolves unsuccessfully
@return {Promise} A promise wrapping the resolution of either "resolve" or
"reject" callback
**/
then: function (callback, errback) {
// using this.constructor allows for customized promises to be
// returned instead of plain ones
var resolve, reject,
promise = new this.constructor(function (res, rej) {
resolve = res;
reject = rej;
});
this._resolver._addCallbacks(
typeof callback === 'function' ?
Promise._makeCallback(promise, resolve, reject, callback) : resolve,
typeof errback === 'function' ?
Promise._makeCallback(promise, resolve, reject, errback) : reject
);
return promise;
},
/*
A shorthand for `promise.then(undefined, callback)`.
Returns a new promise and the error callback gets the same treatment as in
`then`: errors get caught and turned into rejections, and the return value
of the callback becomes the fulfilled value of the returned promise.
@method catch
@param [Function] errback Callback to be called in case this promise is
rejected
@return {Promise} A new promise modified by the behavior of the error
callback
*/
'catch': function (errback) {
return this.then(undefined, errback);
}
});
/**
Wraps the callback in another function to catch exceptions and turn them
into rejections.
@method _makeCallback
@param {Promise} promise Promise that will be affected by this callback
@param {Function} fn Callback to wrap
@return {Function}
@static
@private
**/
Promise._makeCallback = function (promise, resolve, reject, fn) {
// callbacks and errbacks only get one argument
return function (valueOrReason) {
var result;
// Promises model exception handling through callbacks
// making both synchronous and asynchronous errors behave
// the same way
try {
// Use the argument coming in to the callback/errback from the
// resolution of the parent promise.
// The function must be called as a normal function, with no
// special value for |this|, as per Promises A+
result = fn(valueOrReason);
} catch (e) {
// calling return only to stop here
reject(e);
return;
}
if (result === promise) {
reject(new TypeError('Cannot resolve a promise with itself'));
return;
}
resolve(result);
};
};
/*
Ensures that a certain value is a promise. If it is not a promise, it wraps it
in one.
This method can be copied or inherited in subclasses. In that case it will
check that the value passed to it is an instance of the correct class.
This means that `PromiseSubclass.resolve()` will always return instances of
`PromiseSubclass`.
@method resolve
@param {Any} Any object that may or may not be a promise
@return {Promise}
@static
*/
Promise.resolve = function (value) {
if (value && value.constructor === this) {
return value;
}
/*jshint newcap: false */
return new this(function (resolve) {
/*jshint newcap: true */
resolve(value);
});
};
/*
A shorthand for creating a rejected promise.
@method reject
@param {Any} reason Reason for the rejection of this promise. Usually an Error
Object
@return {Promise} A rejected promise
@static
*/
Promise.reject = function (reason) {
/*jshint newcap: false */
var promise = new this(function () {});
/*jshint newcap: true */
// Do not go through resolver.reject() because an immediately rejected promise
// always has no callbacks which would trigger an unnecessary warning
promise._resolver._result = reason;
promise._resolver._status = 'rejected';
return promise;
};
/*
Returns a promise that is resolved or rejected when all values are resolved or
any is rejected. This is useful for waiting for the resolution of multiple
promises, such as reading multiple files in Node.js or making multiple XHR
requests in the browser.
@method all
@param {Any[]} values An array of any kind of values, promises or not. If a value is not
@return [Promise] A promise for an array of all the fulfillment values
@static
*/
Promise.all = function (values) {
var Promise = this;
return new Promise(function (resolve, reject) {
if (!isArray(values)) {
reject(new TypeError('Promise.all expects an array of values or promises'));
return;
}
var remaining = values.length,
i = 0,
length = values.length,
results = [];
function oneDone(index) {
return function (value) {
results[index] = value;
remaining--;
if (!remaining) {
resolve(results);
}
};
}
if (length < 1) {
return resolve(results);
}
for (; i < length; i++) {
Promise.resolve(values[i]).then(oneDone(i), reject);
}
});
};
/*
Returns a promise that is resolved or rejected when any of values is either
resolved or rejected. Can be used for providing early feedback in the UI
while other operations are still pending.
@method race
@param {Any[]} values An array of values or promises
@return {Promise}
@static
*/
Promise.race = function (values) {
var Promise = this;
return new Promise(function (resolve, reject) {
if (!isArray(values)) {
reject(new TypeError('Promise.race expects an array of values or promises'));
return;
}
// just go through the list and resolve and reject at the first change
// This abuses the fact that calling resolve/reject multiple times
// doesn't change the state of the returned promise
for (var i = 0, count = values.length; i < count; i++) {
Promise.resolve(values[i]).then(resolve, reject);
}
});
};
/**
Forces a function to be run asynchronously, but as fast as possible. In Node.js
this is achieved using `setImmediate` or `process.nextTick`. In YUI this is
replaced with `Y.soon`.
@method async
@param {Function} callback The function to call asynchronously
@static
**/
/* istanbul ignore next */
Promise.async = typeof setImmediate !== 'undefined' ?
function (fn) {setImmediate(fn);} :
typeof process !== 'undefined' && process.nextTick ?
process.nextTick :
function (fn) {setTimeout(fn, 0);};
/**
Represents an asynchronous operation. Provides a
standard API for subscribing to the moment that the operation completes either
successfully (`fulfill()`) or unsuccessfully (`reject()`).
@class Promise.Resolver
@constructor
**/
function Resolver() {
/**
List of success callbacks
@property _callbacks
@type Array
@private
**/
this._callbacks = [];
/**
List of failure callbacks
@property _errbacks
@type Array
@private
**/
this._errbacks = [];
/**
The status of the operation. This property may take only one of the following
values: 'pending', 'fulfilled' or 'rejected'.
@property _status
@type String
@default 'pending'
@private
**/
this._status = 'pending';
/**
This value that this promise represents.
@property _result
@type Any
@private
**/
this._result = null;
}
assign(Resolver.prototype, {
/**
Resolves the promise, signaling successful completion of the
represented operation. All "onFulfilled" subscriptions are executed and passed
the value provided to this method. After calling `fulfill()`, `reject()` and
`notify()` are disabled.
@method fulfill
@param {Any} value Value to pass along to the "onFulfilled" subscribers
**/
fulfill: function (value) {
var status = this._status;
if (status === 'pending' || status === 'accepted') {
this._result = value;
this._status = 'fulfilled';
}
if (this._status === 'fulfilled') {
this._notify(this._callbacks, this._result);
// Reset the callback list so that future calls to fulfill()
// won't call the same callbacks again. Promises keep a list
// of callbacks, they're not the same as events. In practice,
// calls to fulfill() after the first one should not be made by
// the user but by then()
this._callbacks = [];
// Once a promise gets fulfilled it can't be rejected, so
// there is no point in keeping the list. Remove it to help
// garbage collection
this._errbacks = null;
}
},
/**
Resolves the promise, signaling *un*successful completion of the
represented operation. All "onRejected" subscriptions are executed with
the value provided to this method. After calling `reject()`, `resolve()`
and `notify()` are disabled.
@method reject
@param {Any} reason Value to pass along to the "reject" subscribers
**/
reject: function (reason) {
var status = this._status;
if (status === 'pending' || status === 'accepted') {
this._result = reason;
this._status = 'rejected';
}
if (this._status === 'rejected') {
this._notify(this._errbacks, this._result);
// See fulfill()
this._callbacks = null;
this._errbacks = [];
}
},
/*
Given a certain value A passed as a parameter, this method resolves the
promise to the value A.
If A is a promise, `resolve` will cause the resolver to adopt the state of A
and once A is resolved, it will resolve the resolver's promise as well.
This behavior "flattens" A by calling `then` recursively and essentially
disallows promises-for-promises.
This is the default algorithm used when using the function passed as the
first argument to the promise initialization function. This means that
the following code returns a promise for the value 'hello world':
var promise1 = new Promise(function (resolve) {
resolve('hello world');
});
var promise2 = new Promise(function (resolve) {
resolve(promise1);
});
promise2.then(function (value) {
assert(value === 'hello world'); // true
});
@method resolve
@param [Any] value A regular JS value or a promise
*/
resolve: function (value) {
if (this._status === 'pending') {
this._status = 'accepted';
this._value = value;
if ((this._callbacks && this._callbacks.length) ||
(this._errbacks && this._errbacks.length)) {
this._unwrap(this._value);
}
}
},
/**
If `value` is a promise or a thenable, it will be unwrapped by
recursively calling its `then` method. If not, the resolver will be
fulfilled with `value`.
This method is called when the promise's `then` method is called and
not in `resolve` to allow for lazy promises to be accepted and not
resolved immediately.
@method _unwrap
@param {Any} value A promise, thenable or regular value
@private
**/
_unwrap: function (value) {
var self = this, unwrapped = false, then;
if (!value || (typeof value !== 'object' &&
typeof value !== 'function')) {
self.fulfill(value);
return;
}
try {
then = value.then;
if (typeof then === 'function') {
then.call(value, function (value) {
if (!unwrapped) {
unwrapped = true;
self._unwrap(value);
}
}, function (reason) {
if (!unwrapped) {
unwrapped = true;
self.reject(reason);
}
});
} else {
self.fulfill(value);
}
} catch (e) {
if (!unwrapped) {
self.reject(e);
}
}
},
/**
Schedule execution of a callback to either or both of "resolve" and
"reject" resolutions of this resolver. If the resolver is not pending,
the correct callback gets called automatically.
@method _addCallbacks
@param {Function} [callback] function to execute if the Resolver
resolves successfully
@param {Function} [errback] function to execute if the Resolver
resolves unsuccessfully
**/
_addCallbacks: function (callback, errback) {
var callbackList = this._callbacks,
errbackList = this._errbacks;
// Because the callback and errback are represented by a Resolver, it
// must be fulfilled or rejected to propagate through the then() chain.
// The same logic applies to resolve() and reject() for fulfillment.
if (callbackList) {
callbackList.push(callback);
}
if (errbackList) {
errbackList.push(errback);
}
switch (this._status) {
case 'accepted':
this._unwrap(this._value);
break;
case 'fulfilled':
this.fulfill(this._result);
break;
case 'rejected':
this.reject(this._result);
break;
}
},
/**
Executes an array of callbacks from a specified context, passing a set of
arguments.
@method _notify
@param {Function[]} subs The array of subscriber callbacks
@param {Any} result Value to pass the callbacks
@protected
**/
_notify: function (subs, result) {
// Since callback lists are reset synchronously, the subs list never
// changes after _notify() receives it. Avoid calling Y.soon() for
// an empty list
if (subs.length) {
// Calling all callbacks after Promise.async to guarantee
// asynchronicity. Because setTimeout can cause unnecessary
// delays that *can* become noticeable in some situations
// (especially in Node.js)
Promise.async(function () {
var i, len;
for (i = 0, len = subs.length; i < len; ++i) {
subs[i](result);
}
});
}
}
});
Promise.Resolver = Resolver;
return Promise;
}));