UNPKG

18.6 kBJavaScriptView Raw
1/** @license MIT License (c) copyright 2010-2014 original author or authors */
2/** @author Brian Cavalier */
3/** @author John Hann */
4
5(function(define) { 'use strict';
6define(function() {
7
8 return function makePromise(environment) {
9
10 var tasks = environment.scheduler;
11
12 var objectCreate = Object.create ||
13 function(proto) {
14 function Child() {}
15 Child.prototype = proto;
16 return new Child();
17 };
18
19 /**
20 * Create a promise whose fate is determined by resolver
21 * @constructor
22 * @returns {Promise} promise
23 * @name Promise
24 */
25 function Promise(resolver, handler) {
26 this._handler = resolver === Handler ? handler : init(resolver);
27 }
28
29 /**
30 * Run the supplied resolver
31 * @param resolver
32 * @returns {Pending}
33 */
34 function init(resolver) {
35 var handler = new Pending();
36
37 try {
38 resolver(promiseResolve, promiseReject, promiseNotify);
39 } catch (e) {
40 promiseReject(e);
41 }
42
43 return handler;
44
45 /**
46 * Transition from pre-resolution state to post-resolution state, notifying
47 * all listeners of the ultimate fulfillment or rejection
48 * @param {*} x resolution value
49 */
50 function promiseResolve (x) {
51 handler.resolve(x);
52 }
53 /**
54 * Reject this promise with reason, which will be used verbatim
55 * @param {Error|*} reason rejection reason, strongly suggested
56 * to be an Error type
57 */
58 function promiseReject (reason) {
59 handler.reject(reason);
60 }
61
62 /**
63 * Issue a progress event, notifying all progress listeners
64 * @param {*} x progress event payload to pass to all listeners
65 */
66 function promiseNotify (x) {
67 handler.notify(x);
68 }
69 }
70
71 // Creation
72
73 Promise.resolve = resolve;
74 Promise.reject = reject;
75 Promise.never = never;
76
77 Promise._defer = defer;
78 Promise._handler = getHandler;
79
80 /**
81 * Returns a trusted promise. If x is already a trusted promise, it is
82 * returned, otherwise returns a new trusted Promise which follows x.
83 * @param {*} x
84 * @return {Promise} promise
85 */
86 function resolve(x) {
87 return isPromise(x) ? x
88 : new Promise(Handler, new Async(getHandler(x)));
89 }
90
91 /**
92 * Return a reject promise with x as its reason (x is used verbatim)
93 * @param {*} x
94 * @returns {Promise} rejected promise
95 */
96 function reject(x) {
97 return new Promise(Handler, new Async(new Rejected(x)));
98 }
99
100 /**
101 * Return a promise that remains pending forever
102 * @returns {Promise} forever-pending promise.
103 */
104 function never() {
105 return foreverPendingPromise; // Should be frozen
106 }
107
108 /**
109 * Creates an internal {promise, resolver} pair
110 * @private
111 * @returns {Promise}
112 */
113 function defer() {
114 return new Promise(Handler, new Pending());
115 }
116
117 // Transformation and flow control
118
119 /**
120 * Transform this promise's fulfillment value, returning a new Promise
121 * for the transformed result. If the promise cannot be fulfilled, onRejected
122 * is called with the reason. onProgress *may* be called with updates toward
123 * this promise's fulfillment.
124 * @param {function=} onFulfilled fulfillment handler
125 * @param {function=} onRejected rejection handler
126 * @deprecated @param {function=} onProgress progress handler
127 * @return {Promise} new promise
128 */
129 Promise.prototype.then = function(onFulfilled, onRejected) {
130 var parent = this._handler;
131
132 if (typeof onFulfilled !== 'function' && parent.join().state() > 0) {
133 // Short circuit: value will not change, simply share handler
134 return new Promise(Handler, parent);
135 }
136
137 var p = this._beget();
138 var child = p._handler;
139
140 parent.chain(child, parent.receiver, onFulfilled, onRejected,
141 arguments.length > 2 ? arguments[2] : void 0);
142
143 return p;
144 };
145
146 /**
147 * If this promise cannot be fulfilled due to an error, call onRejected to
148 * handle the error. Shortcut for .then(undefined, onRejected)
149 * @param {function?} onRejected
150 * @return {Promise}
151 */
152 Promise.prototype['catch'] = function(onRejected) {
153 return this.then(void 0, onRejected);
154 };
155
156 /**
157 * Creates a new, pending promise of the same type as this promise
158 * @private
159 * @returns {Promise}
160 */
161 Promise.prototype._beget = function() {
162 var parent = this._handler;
163 var child = new Pending(parent.receiver, parent.join().context);
164 return new this.constructor(Handler, child);
165 };
166
167 // Array combinators
168
169 Promise.all = all;
170 Promise.race = race;
171
172 /**
173 * Return a promise that will fulfill when all promises in the
174 * input array have fulfilled, or will reject when one of the
175 * promises rejects.
176 * @param {array} promises array of promises
177 * @returns {Promise} promise for array of fulfillment values
178 */
179 function all(promises) {
180 /*jshint maxcomplexity:8*/
181 var resolver = new Pending();
182 var pending = promises.length >>> 0;
183 var results = new Array(pending);
184
185 var i, h, x, s;
186 for (i = 0; i < promises.length; ++i) {
187 x = promises[i];
188
189 if (x === void 0 && !(i in promises)) {
190 --pending;
191 continue;
192 }
193
194 if (maybeThenable(x)) {
195 h = isPromise(x)
196 ? x._handler.join()
197 : getHandlerUntrusted(x);
198
199 s = h.state();
200 if (s === 0) {
201 h.fold(settleAt, i, results, resolver);
202 } else if (s > 0) {
203 results[i] = h.value;
204 --pending;
205 } else {
206 resolver.become(h);
207 break;
208 }
209
210 } else {
211 results[i] = x;
212 --pending;
213 }
214 }
215
216 if(pending === 0) {
217 resolver.become(new Fulfilled(results));
218 }
219
220 return new Promise(Handler, resolver);
221
222 function settleAt(i, x, resolver) {
223 /*jshint validthis:true*/
224 this[i] = x;
225 if(--pending === 0) {
226 resolver.become(new Fulfilled(this));
227 }
228 }
229 }
230
231 /**
232 * Fulfill-reject competitive race. Return a promise that will settle
233 * to the same state as the earliest input promise to settle.
234 *
235 * WARNING: The ES6 Promise spec requires that race()ing an empty array
236 * must return a promise that is pending forever. This implementation
237 * returns a singleton forever-pending promise, the same singleton that is
238 * returned by Promise.never(), thus can be checked with ===
239 *
240 * @param {array} promises array of promises to race
241 * @returns {Promise} if input is non-empty, a promise that will settle
242 * to the same outcome as the earliest input promise to settle. if empty
243 * is empty, returns a promise that will never settle.
244 */
245 function race(promises) {
246 // Sigh, race([]) is untestable unless we return *something*
247 // that is recognizable without calling .then() on it.
248 if(Object(promises) === promises && promises.length === 0) {
249 return never();
250 }
251
252 var h = new Pending();
253 var i, x;
254 for(i=0; i<promises.length; ++i) {
255 x = promises[i];
256 if (x !== void 0 && i in promises) {
257 getHandler(x).visit(h, h.resolve, h.reject);
258 }
259 }
260 return new Promise(Handler, h);
261 }
262
263 // Promise internals
264 // Below this, everything is @private
265
266 /**
267 * Get an appropriate handler for x, without checking for cycles
268 * @param {*} x
269 * @returns {object} handler
270 */
271 function getHandler(x) {
272 if(isPromise(x)) {
273 return x._handler.join();
274 }
275 return maybeThenable(x) ? getHandlerUntrusted(x) : new Fulfilled(x);
276 }
277
278 /**
279 * Get a handler for potentially untrusted thenable x
280 * @param {*} x
281 * @returns {object} handler
282 */
283 function getHandlerUntrusted(x) {
284 try {
285 var untrustedThen = x.then;
286 return typeof untrustedThen === 'function'
287 ? new Thenable(untrustedThen, x)
288 : new Fulfilled(x);
289 } catch(e) {
290 return new Rejected(e);
291 }
292 }
293
294 /**
295 * Handler for a promise that is pending forever
296 * @constructor
297 */
298 function Handler() {}
299
300 Handler.prototype.when
301 = Handler.prototype.become
302 = Handler.prototype.notify
303 = Handler.prototype.fail
304 = Handler.prototype._unreport
305 = Handler.prototype._report
306 = noop;
307
308 Handler.prototype._state = 0;
309
310 Handler.prototype.state = function() {
311 return this._state;
312 };
313
314 /**
315 * Recursively collapse handler chain to find the handler
316 * nearest to the fully resolved value.
317 * @returns {object} handler nearest the fully resolved value
318 */
319 Handler.prototype.join = function() {
320 var h = this;
321 while(h.handler !== void 0) {
322 h = h.handler;
323 }
324 return h;
325 };
326
327 Handler.prototype.chain = function(to, receiver, fulfilled, rejected, progress) {
328 this.when({
329 resolver: to,
330 receiver: receiver,
331 fulfilled: fulfilled,
332 rejected: rejected,
333 progress: progress
334 });
335 };
336
337 Handler.prototype.visit = function(receiver, fulfilled, rejected, progress) {
338 this.chain(failIfRejected, receiver, fulfilled, rejected, progress);
339 };
340
341 Handler.prototype.fold = function(f, z, c, to) {
342 this.visit(to, function(x) {
343 f.call(c, z, x, this);
344 }, to.reject, to.notify);
345 };
346
347 /**
348 * Handler that invokes fail() on any handler it becomes
349 * @constructor
350 */
351 function FailIfRejected() {}
352
353 inherit(Handler, FailIfRejected);
354
355 FailIfRejected.prototype.become = function(h) {
356 h.fail();
357 };
358
359 var failIfRejected = new FailIfRejected();
360
361 /**
362 * Handler that manages a queue of consumers waiting on a pending promise
363 * @constructor
364 */
365 function Pending(receiver, inheritedContext) {
366 Promise.createContext(this, inheritedContext);
367
368 this.consumers = void 0;
369 this.receiver = receiver;
370 this.handler = void 0;
371 this.resolved = false;
372 }
373
374 inherit(Handler, Pending);
375
376 Pending.prototype._state = 0;
377
378 Pending.prototype.resolve = function(x) {
379 this.become(getHandler(x));
380 };
381
382 Pending.prototype.reject = function(x) {
383 if(this.resolved) {
384 return;
385 }
386
387 this.become(new Rejected(x));
388 };
389
390 Pending.prototype.join = function() {
391 if (!this.resolved) {
392 return this;
393 }
394
395 var h = this;
396
397 while (h.handler !== void 0) {
398 h = h.handler;
399 if (h === this) {
400 return this.handler = cycle();
401 }
402 }
403
404 return h;
405 };
406
407 Pending.prototype.run = function() {
408 var q = this.consumers;
409 var handler = this.join();
410 this.consumers = void 0;
411
412 for (var i = 0; i < q.length; ++i) {
413 handler.when(q[i]);
414 }
415 };
416
417 Pending.prototype.become = function(handler) {
418 if(this.resolved) {
419 return;
420 }
421
422 this.resolved = true;
423 this.handler = handler;
424 if(this.consumers !== void 0) {
425 tasks.enqueue(this);
426 }
427
428 if(this.context !== void 0) {
429 handler._report(this.context);
430 }
431 };
432
433 Pending.prototype.when = function(continuation) {
434 if(this.resolved) {
435 tasks.enqueue(new ContinuationTask(continuation, this.handler));
436 } else {
437 if(this.consumers === void 0) {
438 this.consumers = [continuation];
439 } else {
440 this.consumers.push(continuation);
441 }
442 }
443 };
444
445 Pending.prototype.notify = function(x) {
446 if(!this.resolved) {
447 tasks.enqueue(new ProgressTask(x, this));
448 }
449 };
450
451 Pending.prototype.fail = function(context) {
452 var c = typeof context === 'undefined' ? this.context : context;
453 this.resolved && this.handler.join().fail(c);
454 };
455
456 Pending.prototype._report = function(context) {
457 this.resolved && this.handler.join()._report(context);
458 };
459
460 Pending.prototype._unreport = function() {
461 this.resolved && this.handler.join()._unreport();
462 };
463
464 /**
465 * Abstract base for handler that delegates to another handler
466 * @param {object} handler
467 * @constructor
468 */
469 function Delegating(handler) {
470 this.handler = handler;
471 }
472
473 inherit(Handler, Delegating);
474
475 Delegating.prototype._report = function(context) {
476 this.join()._report(context);
477 };
478
479 Delegating.prototype._unreport = function() {
480 this.join()._unreport();
481 };
482
483 /**
484 * Wrap another handler and force it into a future stack
485 * @param {object} handler
486 * @constructor
487 */
488 function Async(handler) {
489 Delegating.call(this, handler);
490 }
491
492 inherit(Delegating, Async);
493
494 Async.prototype.when = function(continuation) {
495 tasks.enqueue(new ContinuationTask(continuation, this));
496 };
497
498 /**
499 * Handler that wraps an untrusted thenable and assimilates it in a future stack
500 * @param {function} then
501 * @param {{then: function}} thenable
502 * @constructor
503 */
504 function Thenable(then, thenable) {
505 Pending.call(this);
506 tasks.enqueue(new AssimilateTask(then, thenable, this));
507 }
508
509 inherit(Pending, Thenable);
510
511 /**
512 * Handler for a fulfilled promise
513 * @param {*} x fulfillment value
514 * @constructor
515 */
516 function Fulfilled(x) {
517 Promise.createContext(this);
518 this.value = x;
519 }
520
521 inherit(Handler, Fulfilled);
522
523 Fulfilled.prototype._state = 1;
524
525 Fulfilled.prototype.fold = function(f, z, c, to) {
526 runContinuation3(f, z, this, c, to);
527 };
528
529 Fulfilled.prototype.when = function(cont) {
530 runContinuation1(cont.fulfilled, this, cont.receiver, cont.resolver);
531 };
532
533 var errorId = 0;
534
535 /**
536 * Handler for a rejected promise
537 * @param {*} x rejection reason
538 * @constructor
539 */
540 function Rejected(x) {
541 Promise.createContext(this);
542
543 this.id = ++errorId;
544 this.value = x;
545 this.handled = false;
546 this.reported = false;
547
548 this._report();
549 }
550
551 inherit(Handler, Rejected);
552
553 Rejected.prototype._state = -1;
554
555 Rejected.prototype.fold = function(f, z, c, to) {
556 to.become(this);
557 };
558
559 Rejected.prototype.when = function(cont) {
560 if(typeof cont.rejected === 'function') {
561 this._unreport();
562 }
563 runContinuation1(cont.rejected, this, cont.receiver, cont.resolver);
564 };
565
566 Rejected.prototype._report = function(context) {
567 tasks.afterQueue(new ReportTask(this, context));
568 };
569
570 Rejected.prototype._unreport = function() {
571 this.handled = true;
572 tasks.afterQueue(new UnreportTask(this));
573 };
574
575 Rejected.prototype.fail = function(context) {
576 Promise.onFatalRejection(this, context === void 0 ? this.context : context);
577 };
578
579 function ReportTask(rejection, context) {
580 this.rejection = rejection;
581 this.context = context;
582 }
583
584 ReportTask.prototype.run = function() {
585 if(!this.rejection.handled) {
586 this.rejection.reported = true;
587 Promise.onPotentiallyUnhandledRejection(this.rejection, this.context);
588 }
589 };
590
591 function UnreportTask(rejection) {
592 this.rejection = rejection;
593 }
594
595 UnreportTask.prototype.run = function() {
596 if(this.rejection.reported) {
597 Promise.onPotentiallyUnhandledRejectionHandled(this.rejection);
598 }
599 };
600
601 // Unhandled rejection hooks
602 // By default, everything is a noop
603
604 // TODO: Better names: "annotate"?
605 Promise.createContext
606 = Promise.enterContext
607 = Promise.exitContext
608 = Promise.onPotentiallyUnhandledRejection
609 = Promise.onPotentiallyUnhandledRejectionHandled
610 = Promise.onFatalRejection
611 = noop;
612
613 // Errors and singletons
614
615 var foreverPendingHandler = new Handler();
616 var foreverPendingPromise = new Promise(Handler, foreverPendingHandler);
617
618 function cycle() {
619 return new Rejected(new TypeError('Promise cycle'));
620 }
621
622 // Task runners
623
624 /**
625 * Run a single consumer
626 * @constructor
627 */
628 function ContinuationTask(continuation, handler) {
629 this.continuation = continuation;
630 this.handler = handler;
631 }
632
633 ContinuationTask.prototype.run = function() {
634 this.handler.join().when(this.continuation);
635 };
636
637 /**
638 * Run a queue of progress handlers
639 * @constructor
640 */
641 function ProgressTask(value, handler) {
642 this.handler = handler;
643 this.value = value;
644 }
645
646 ProgressTask.prototype.run = function() {
647 var q = this.handler.consumers;
648 if(q === void 0) {
649 return;
650 }
651
652 for (var c, i = 0; i < q.length; ++i) {
653 c = q[i];
654 runNotify(c.progress, this.value, this.handler, c.receiver, c.resolver);
655 }
656 };
657
658 /**
659 * Assimilate a thenable, sending it's value to resolver
660 * @param {function} then
661 * @param {object|function} thenable
662 * @param {object} resolver
663 * @constructor
664 */
665 function AssimilateTask(then, thenable, resolver) {
666 this._then = then;
667 this.thenable = thenable;
668 this.resolver = resolver;
669 }
670
671 AssimilateTask.prototype.run = function() {
672 var h = this.resolver;
673 tryAssimilate(this._then, this.thenable, _resolve, _reject, _notify);
674
675 function _resolve(x) { h.resolve(x); }
676 function _reject(x) { h.reject(x); }
677 function _notify(x) { h.notify(x); }
678 };
679
680 function tryAssimilate(then, thenable, resolve, reject, notify) {
681 try {
682 then.call(thenable, resolve, reject, notify);
683 } catch (e) {
684 reject(e);
685 }
686 }
687
688 // Other helpers
689
690 /**
691 * @param {*} x
692 * @returns {boolean} true iff x is a trusted Promise
693 */
694 function isPromise(x) {
695 return x instanceof Promise;
696 }
697
698 /**
699 * Test just enough to rule out primitives, in order to take faster
700 * paths in some code
701 * @param {*} x
702 * @returns {boolean} false iff x is guaranteed *not* to be a thenable
703 */
704 function maybeThenable(x) {
705 return (typeof x === 'object' || typeof x === 'function') && x !== null;
706 }
707
708 function runContinuation1(f, h, receiver, next) {
709 if(typeof f !== 'function') {
710 return next.become(h);
711 }
712
713 Promise.enterContext(h);
714 tryCatchReject(f, h.value, receiver, next);
715 Promise.exitContext();
716 }
717
718 function runContinuation3(f, x, h, receiver, next) {
719 if(typeof f !== 'function') {
720 return next.become(h);
721 }
722
723 Promise.enterContext(h);
724 tryCatchReject3(f, x, h.value, receiver, next);
725 Promise.exitContext();
726 }
727
728 function runNotify(f, x, h, receiver, next) {
729 if(typeof f !== 'function') {
730 return next.notify(x);
731 }
732
733 Promise.enterContext(h);
734 tryCatchReturn(f, x, receiver, next);
735 Promise.exitContext();
736 }
737
738 /**
739 * Return f.call(thisArg, x), or if it throws return a rejected promise for
740 * the thrown exception
741 */
742 function tryCatchReject(f, x, thisArg, next) {
743 try {
744 next.become(getHandler(f.call(thisArg, x)));
745 } catch(e) {
746 next.become(new Rejected(e));
747 }
748 }
749
750 /**
751 * Same as above, but includes the extra argument parameter.
752 */
753 function tryCatchReject3(f, x, y, thisArg, next) {
754 try {
755 f.call(thisArg, x, y, next);
756 } catch(e) {
757 next.become(new Rejected(e));
758 }
759 }
760
761 /**
762 * Return f.call(thisArg, x), or if it throws, *return* the exception
763 */
764 function tryCatchReturn(f, x, thisArg, next) {
765 try {
766 next.notify(f.call(thisArg, x));
767 } catch(e) {
768 next.notify(e);
769 }
770 }
771
772 function inherit(Parent, Child) {
773 Child.prototype = objectCreate(Parent.prototype);
774 Child.prototype.constructor = Child;
775 }
776
777 function noop() {}
778
779 return Promise;
780 };
781});
782}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));