UNPKG

27.5 kBJavaScriptView Raw
1/*
2 Yaku v1.0.0
3 (c) 2015 Yad Smood. http://ysmood.org
4 License MIT
5*/
6/*
7 Yaku v0.17.9
8 (c) 2015 Yad Smood. http://ysmood.org
9 License MIT
10*/
11(function () {
12 'use strict';
13
14 var $undefined
15 , $null = null
16 , isBrowser = typeof self === 'object'
17 , root = isBrowser ? self : global
18 , nativePromise = root.Promise
19 , process = root.process
20 , console = root.console
21 , isLongStackTrace = false
22 , Arr = Array
23 , Err = Error
24
25 , $rejected = 1
26 , $resolved = 2
27 , $pending = 3
28
29 , $Symbol = 'Symbol'
30 , $iterator = 'iterator'
31 , $species = 'species'
32 , $speciesKey = $Symbol + '(' + $species + ')'
33 , $return = 'return'
34
35 , $unhandled = '_uh'
36 , $promiseTrace = '_pt'
37 , $settlerTrace = '_st'
38
39 , $invalidThis = 'Invalid this'
40 , $invalidArgument = 'Invalid argument'
41 , $fromPrevious = '\nFrom previous '
42 , $promiseCircularChain = 'Chaining cycle detected for promise'
43 , $unhandledRejectionMsg = 'Uncaught (in promise)'
44 , $rejectionHandled = 'rejectionHandled'
45 , $unhandledRejection = 'unhandledRejection'
46
47 , $tryCatchFn
48 , $tryCatchThis
49 , $tryErr = { e: $null }
50 , $noop = function () {}
51 , $cleanStackReg = /^.+\/node_modules\/yaku\/.+\n?/mg
52 ;
53
54 /**
55 * This class follows the [Promises/A+](https://promisesaplus.com) and
56 * [ES6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) spec
57 * with some extra helpers.
58 * @param {Function} executor Function object with two arguments resolve, reject.
59 * The first argument fulfills the promise, the second argument rejects it.
60 * We can call these functions, once our operation is completed.
61 */
62 var Yaku = function (executor) {
63 var self = this,
64 err;
65
66 // "this._s" is the internao state of: pending, resolved or rejected
67 // "this._v" is the internal value
68
69 if (!isObject(self) || self._s !== $undefined)
70 throw genTypeError($invalidThis);
71
72 self._s = $pending;
73
74 if (isLongStackTrace) self[$promiseTrace] = genTraceInfo();
75
76 if (executor !== $noop) {
77 if (!isFunction(executor))
78 throw genTypeError($invalidArgument);
79
80 err = genTryCatcher(executor)(
81 genSettler(self, $resolved),
82 genSettler(self, $rejected)
83 );
84
85 if (err === $tryErr)
86 settlePromise(self, $rejected, err.e);
87 }
88 };
89
90 Yaku['default'] = Yaku;
91
92 extend(Yaku.prototype, {
93 /**
94 * Appends fulfillment and rejection handlers to the promise,
95 * and returns a new promise resolving to the return value of the called handler.
96 * @param {Function} onFulfilled Optional. Called when the Promise is resolved.
97 * @param {Function} onRejected Optional. Called when the Promise is rejected.
98 * @return {Yaku} It will return a new Yaku which will resolve or reject after
99 * @example
100 * the current Promise.
101 * ```js
102 * var Promise = require('yaku');
103 * var p = Promise.resolve(10);
104 *
105 * p.then((v) => {
106 * console.log(v);
107 * });
108 * ```
109 */
110 then: function (onFulfilled, onRejected) {
111 if (this._s === undefined) throw genTypeError();
112
113 return addHandler(
114 this,
115 newCapablePromise(Yaku.speciesConstructor(this, Yaku)),
116 onFulfilled,
117 onRejected
118 );
119 },
120
121 /**
122 * The `catch()` method returns a Promise and deals with rejected cases only.
123 * It behaves the same as calling `Promise.prototype.then(undefined, onRejected)`.
124 * @param {Function} onRejected A Function called when the Promise is rejected.
125 * This function has one argument, the rejection reason.
126 * @return {Yaku} A Promise that deals with rejected cases only.
127 * @example
128 * ```js
129 * var Promise = require('yaku');
130 * var p = Promise.reject(new Error("ERR"));
131 *
132 * p['catch']((v) => {
133 * console.log(v);
134 * });
135 * ```
136 */
137 'catch': function (onRejected) {
138 return this.then($undefined, onRejected);
139 },
140
141 /**
142 * Register a callback to be invoked when a promise is settled (either fulfilled or rejected).
143 * Similar with the try-catch-finally, it's often used for cleanup.
144 * @param {Function} onFinally A Function called when the Promise is settled.
145 * It will not receive any argument.
146 * @return {Yaku} A Promise that will reject if onFinally throws an error or returns a rejected promise.
147 * Else it will resolve previous promise's final state (either fulfilled or rejected).
148 * @example
149 * ```js
150 * var Promise = require('yaku');
151 * var p = Math.random() > 0.5 ? Promise.resolve() : Promise.reject();
152 * p.finally(() => {
153 * console.log('finally');
154 * });
155 * ```
156 */
157 'finally': function (onFinally) {
158 return this.then(function (val) {
159 return Yaku.resolve(onFinally()).then(function () {
160 return val;
161 });
162 }, function (err) {
163 return Yaku.resolve(onFinally()).then(function () {
164 throw err;
165 });
166 });
167 },
168
169 // The number of current promises that attach to this Yaku instance.
170 _c: 0,
171
172 // The parent Yaku.
173 _p: $null
174 });
175
176 /**
177 * The `Promise.resolve(value)` method returns a Promise object that is resolved with the given value.
178 * If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable,
179 * adopting its eventual state; otherwise the returned promise will be fulfilled with the value.
180 * @param {Any} value Argument to be resolved by this Promise.
181 * Can also be a Promise or a thenable to resolve.
182 * @return {Yaku}
183 * @example
184 * ```js
185 * var Promise = require('yaku');
186 * var p = Promise.resolve(10);
187 * ```
188 */
189 Yaku.resolve = function (val) {
190 return isYaku(val) ? val : settleWithX(newCapablePromise(this), val);
191 };
192
193 /**
194 * The `Promise.reject(reason)` method returns a Promise object that is rejected with the given reason.
195 * @param {Any} reason Reason why this Promise rejected.
196 * @return {Yaku}
197 * @example
198 * ```js
199 * var Promise = require('yaku');
200 * var p = Promise.reject(new Error("ERR"));
201 * ```
202 */
203 Yaku.reject = function (reason) {
204 return settlePromise(newCapablePromise(this), $rejected, reason);
205 };
206
207 /**
208 * The `Promise.race(iterable)` method returns a promise that resolves or rejects
209 * as soon as one of the promises in the iterable resolves or rejects,
210 * with the value or reason from that promise.
211 * @param {iterable} iterable An iterable object, such as an Array.
212 * @return {Yaku} The race function returns a Promise that is settled
213 * the same way as the first passed promise to settle.
214 * It resolves or rejects, whichever happens first.
215 * @example
216 * ```js
217 * var Promise = require('yaku');
218 * Promise.race([
219 * 123,
220 * Promise.resolve(0)
221 * ])
222 * .then((value) => {
223 * console.log(value); // => 123
224 * });
225 * ```
226 */
227 Yaku.race = function (iterable) {
228 var self = this
229 , p = newCapablePromise(self)
230
231 , resolve = function (val) {
232 settlePromise(p, $resolved, val);
233 }
234
235 , reject = function (val) {
236 settlePromise(p, $rejected, val);
237 }
238
239 , ret = genTryCatcher(each)(iterable, function (v) {
240 self.resolve(v).then(resolve, reject);
241 });
242
243 if (ret === $tryErr) return self.reject(ret.e);
244
245 return p;
246 };
247
248 /**
249 * The `Promise.all(iterable)` method returns a promise that resolves when
250 * all of the promises in the iterable argument have resolved.
251 *
252 * The result is passed as an array of values from all the promises.
253 * If something passed in the iterable array is not a promise,
254 * it's converted to one by Promise.resolve. If any of the passed in promises rejects,
255 * the all Promise immediately rejects with the value of the promise that rejected,
256 * discarding all the other promises whether or not they have resolved.
257 * @param {iterable} iterable An iterable object, such as an Array.
258 * @return {Yaku}
259 * @example
260 * ```js
261 * var Promise = require('yaku');
262 * Promise.all([
263 * 123,
264 * Promise.resolve(0)
265 * ])
266 * .then((values) => {
267 * console.log(values); // => [123, 0]
268 * });
269 * ```
270 * @example
271 * Use with iterable.
272 * ```js
273 * var Promise = require('yaku');
274 * Promise.all((function * () {
275 * yield 10;
276 * yield new Promise(function (r) { setTimeout(r, 1000, "OK") });
277 * })())
278 * .then((values) => {
279 * console.log(values); // => [123, 0]
280 * });
281 * ```
282 */
283 Yaku.all = function (iterable) {
284 var self = this
285 , p = newCapablePromise(self)
286 , res = []
287 , ret
288 ;
289
290 function reject (reason) {
291 settlePromise(p, $rejected, reason);
292 }
293
294 ret = genTryCatcher(each)(iterable, function (item, i) {
295 self.resolve(item).then(function (value) {
296 res[i] = value;
297 if (!--ret) settlePromise(p, $resolved, res);
298 }, reject);
299 });
300
301 if (ret === $tryErr) return self.reject(ret.e);
302
303 if (!ret) settlePromise(p, $resolved, []);
304
305 return p;
306 };
307
308 /**
309 * The `Promise.allSettled(iterable)` method returns a promise that resolves after all
310 * of the given promises have either resolved or rejected, with an array of objects that
311 * each describes the outcome of each promise.
312 * @param {iterable} iterable An iterable object, such as an Array.
313 * @return {Yaku} A promise resolves a list of objects. For each object, a status string is present.
314 * If the status is fulfilled, then a value is present. If the status is rejected, then a reason is present.
315 * The value (or reason) reflects what value each promise was fulfilled (or rejected) with.
316 * @example
317 * ```js
318 * var Promise = require('yaku');
319 * Promise.allSettled([
320 * Promise.resolve(3),
321 * new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'))
322 * ])
323 * .then((values) => {
324 * console.log(values); // => [{status: "fulfilled", value: 3}, {status: "rejected", reason: "foo"}]
325 * });
326 * ```
327 */
328 Yaku.allSettled = function (iterable) {
329 var self = this
330 , p = newCapablePromise(self)
331 , res = []
332 , ret
333 ;
334
335 ret = genTryCatcher(each)(iterable, function (item, i) {
336 self.resolve(item).then(function (value) {
337 res[i] = { status: 'fulfilled', value: value };
338 if (!--ret) settlePromise(p, $resolved, res);
339 }, function (value) {
340 res[i] = { status: 'rejected', reason: value };
341 if (!--ret) settlePromise(p, $resolved, res);
342 });
343 });
344
345 if (ret === $tryErr) return self.reject(ret.e);
346
347 if (!ret) settlePromise(p, $resolved, []);
348
349 return p;
350 };
351
352 /**
353 * The ES6 Symbol object that Yaku should use, by default it will use the
354 * global one.
355 * @type {Object}
356 * @example
357 * ```js
358 * var core = require("core-js/library");
359 * var Promise = require("yaku");
360 * Promise.Symbol = core.Symbol;
361 * ```
362 */
363 Yaku.Symbol = root[$Symbol] || {};
364
365 // To support browsers that don't support `Object.defineProperty`.
366 genTryCatcher(function () {
367 Object.defineProperty(Yaku, getSpecies(), {
368 get: function () { return this; }
369 });
370 })();
371
372 /**
373 * Use this api to custom the species behavior.
374 * https://tc39.github.io/ecma262/#sec-speciesconstructor
375 * @param {Any} O The current this object.
376 * @param {Function} defaultConstructor
377 */
378 Yaku.speciesConstructor = function (O, D) {
379 var C = O.constructor;
380
381 return C ? (C[getSpecies()] || D) : D;
382 };
383
384 /**
385 * Catch all possibly unhandled rejections. If you want to use specific
386 * format to display the error stack, overwrite it.
387 * If it is set, auto `console.error` unhandled rejection will be disabled.
388 * @param {Any} reason The rejection reason.
389 * @param {Yaku} p The promise that was rejected.
390 * @example
391 * ```js
392 * var Promise = require('yaku');
393 * Promise.unhandledRejection = (reason) => {
394 * console.error(reason);
395 * };
396 *
397 * // The console will log an unhandled rejection error message.
398 * Promise.reject('my reason');
399 *
400 * // The below won't log the unhandled rejection error message.
401 * Promise.reject('v')["catch"](() => {});
402 * ```
403 */
404 Yaku.unhandledRejection = function (reason, p) {
405 console && console.error(
406 $unhandledRejectionMsg,
407 isLongStackTrace ? p.longStack : genStackInfo(reason, p)
408 );
409 };
410
411 /**
412 * Emitted whenever a Promise was rejected and an error handler was
413 * attached to it (for example with `["catch"]()`) later than after an event loop turn.
414 * @param {Any} reason The rejection reason.
415 * @param {Yaku} p The promise that was rejected.
416 */
417 Yaku.rejectionHandled = $noop;
418
419 /**
420 * It is used to enable the long stack trace.
421 * Once it is enabled, it can't be reverted.
422 * While it is very helpful in development and testing environments,
423 * it is not recommended to use it in production. It will slow down
424 * application and eat up memory.
425 * It will add an extra property `longStack` to the Error object.
426 * @example
427 * ```js
428 * var Promise = require('yaku');
429 * Promise.enableLongStackTrace();
430 * Promise.reject(new Error("err"))["catch"]((err) => {
431 * console.log(err.longStack);
432 * });
433 * ```
434 */
435 Yaku.enableLongStackTrace = function () {
436 isLongStackTrace = true;
437 };
438
439 /**
440 * Only Node has `process.nextTick` function. For browser there are
441 * so many ways to polyfill it. Yaku won't do it for you, instead you
442 * can choose what you prefer. For example, this project
443 * [next-tick](https://github.com/medikoo/next-tick).
444 * By default, Yaku will use `process.nextTick` on Node, `setTimeout` on browser.
445 * @type {Function}
446 * @example
447 * ```js
448 * var Promise = require('yaku');
449 * Promise.nextTick = require('next-tick');
450 * ```
451 * @example
452 * You can even use sync resolution if you really know what you are doing.
453 * ```js
454 * var Promise = require('yaku');
455 * Promise.nextTick = fn => fn();
456 * ```
457 */
458 Yaku.nextTick = isBrowser ?
459 function (fn) {
460 nativePromise ?
461 new nativePromise(function (resolve) { resolve(); }).then(fn) :
462 setTimeout(fn);
463 } :
464 process.nextTick;
465
466 // ********************** Private **********************
467
468 Yaku._s = 1;
469
470 /**
471 * All static variable name will begin with `$`. Such as `$rejected`.
472 * @private
473 */
474
475 // ******************************* Utils ********************************
476
477 function getSpecies () {
478 return Yaku[$Symbol][$species] || $speciesKey;
479 }
480
481 function extend (src, target) {
482 for (var k in target) {
483 src[k] = target[k];
484 }
485 }
486
487 function isObject (obj) {
488 return obj && typeof obj === 'object';
489 }
490
491 function isFunction (obj) {
492 return typeof obj === 'function';
493 }
494
495 function isInstanceOf (a, b) {
496 return a instanceof b;
497 }
498
499 function isError (obj) {
500 return isInstanceOf(obj, Err);
501 }
502
503 function ensureType (obj, fn, msg) {
504 if (!fn(obj)) throw genTypeError(msg);
505 }
506
507 /**
508 * Wrap a function into a try-catch.
509 * @private
510 * @return {Any | $tryErr}
511 */
512 function tryCatcher () {
513 try {
514 return $tryCatchFn.apply($tryCatchThis, arguments);
515 } catch (e) {
516 $tryErr.e = e;
517 return $tryErr;
518 }
519 }
520
521 /**
522 * Generate a try-catch wrapped function.
523 * @private
524 * @param {Function} fn
525 * @return {Function}
526 */
527 function genTryCatcher (fn, self) {
528 $tryCatchFn = fn;
529 $tryCatchThis = self;
530 return tryCatcher;
531 }
532
533 /**
534 * Generate a scheduler.
535 * @private
536 * @param {Integer} initQueueSize
537 * @param {Function} fn `(Yaku, Value) ->` The schedule handler.
538 * @return {Function} `(Yaku, Value) ->` The scheduler.
539 */
540 function genScheduler (initQueueSize, fn) {
541 /**
542 * All async promise will be scheduled in
543 * here, so that they can be execute on the next tick.
544 * @private
545 */
546 var fnQueue = Arr(initQueueSize)
547 , fnQueueLen = 0;
548
549 /**
550 * Run all queued functions.
551 * @private
552 */
553 function flush () {
554 var i = 0;
555 while (i < fnQueueLen) {
556 fn(fnQueue[i], fnQueue[i + 1]);
557 fnQueue[i++] = $undefined;
558 fnQueue[i++] = $undefined;
559 }
560
561 fnQueueLen = 0;
562 if (fnQueue.length > initQueueSize) fnQueue.length = initQueueSize;
563 }
564
565 return function (v, arg) {
566 fnQueue[fnQueueLen++] = v;
567 fnQueue[fnQueueLen++] = arg;
568
569 if (fnQueueLen === 2) Yaku.nextTick(flush);
570 };
571 }
572
573 /**
574 * Generate a iterator
575 * @param {Any} obj
576 * @private
577 * @return {Object || TypeError}
578 */
579 function each (iterable, fn) {
580 var len
581 , i = 0
582 , iter
583 , item
584 , ret
585 ;
586
587 if (!iterable) throw genTypeError($invalidArgument);
588
589 var gen = iterable[Yaku[$Symbol][$iterator]];
590 if (isFunction(gen))
591 iter = gen.call(iterable);
592 else if (isFunction(iterable.next)) {
593 iter = iterable;
594 }
595 else if (isInstanceOf(iterable, Arr)) {
596 len = iterable.length;
597 while (i < len) {
598 fn(iterable[i], i++);
599 }
600 return i;
601 } else
602 throw genTypeError($invalidArgument);
603
604 while (!(item = iter.next()).done) {
605 ret = genTryCatcher(fn)(item.value, i++);
606 if (ret === $tryErr) {
607 isFunction(iter[$return]) && iter[$return]();
608 throw ret.e;
609 }
610 }
611
612 return i;
613 }
614
615 /**
616 * Generate type error object.
617 * @private
618 * @param {String} msg
619 * @return {TypeError}
620 */
621 function genTypeError (msg) {
622 return new TypeError(msg);
623 }
624
625 function genTraceInfo (noTitle) {
626 return (noTitle ? '' : $fromPrevious) + new Err().stack;
627 }
628
629
630 // *************************** Promise Helpers ****************************
631
632 /**
633 * Resolve the value returned by onFulfilled or onRejected.
634 * @private
635 * @param {Yaku} p1
636 * @param {Yaku} p2
637 */
638 var scheduleHandler = genScheduler(999, function (p1, p2) {
639 var x, handler;
640
641 // 2.2.2
642 // 2.2.3
643 handler = p1._s !== $rejected ? p2._onFulfilled : p2._onRejected;
644
645 // 2.2.7.3
646 // 2.2.7.4
647 if (handler === $undefined) {
648 settlePromise(p2, p1._s, p1._v);
649 return;
650 }
651
652 // 2.2.7.1
653 x = genTryCatcher(callHanler)(handler, p1._v);
654 if (x === $tryErr) {
655 // 2.2.7.2
656 settlePromise(p2, $rejected, x.e);
657 return;
658 }
659
660 settleWithX(p2, x);
661 });
662
663 var scheduleUnhandledRejection = genScheduler(9, function (p) {
664 if (!hashOnRejected(p)) {
665 p[$unhandled] = 1;
666 emitEvent($unhandledRejection, p);
667 }
668 });
669
670 function emitEvent (name, p) {
671 var browserEventName = 'on' + name.toLowerCase()
672 , browserHandler = root[browserEventName];
673
674 if (process && process.listeners(name).length)
675 name === $unhandledRejection ?
676 process.emit(name, p._v, p) : process.emit(name, p);
677 else if (browserHandler)
678 browserHandler({ reason: p._v, promise: p });
679 else
680 Yaku[name](p._v, p);
681 }
682
683 function isYaku (val) { return val && val._s; }
684
685 function newCapablePromise (Constructor) {
686 if (isYaku(Constructor)) return new Constructor($noop);
687
688 var p, r, j;
689 p = new Constructor(function (resolve, reject) {
690 if (p) throw genTypeError();
691
692 r = resolve;
693 j = reject;
694 });
695
696 ensureType(r, isFunction);
697 ensureType(j, isFunction);
698
699 return p;
700 }
701
702 /**
703 * It will produce a settlePromise function to user.
704 * Such as the resolve and reject in this `new Yaku (resolve, reject) ->`.
705 * @private
706 * @param {Yaku} self
707 * @param {Integer} state The value is one of `$pending`, `$resolved` or `$rejected`.
708 * @return {Function} `(value) -> undefined` A resolve or reject function.
709 */
710 function genSettler (self, state) {
711 var isCalled = false;
712 return function (value) {
713 if (isCalled) return;
714 isCalled = true;
715
716 if (isLongStackTrace)
717 self[$settlerTrace] = genTraceInfo(true);
718
719 if (state === $resolved)
720 settleWithX(self, value);
721 else
722 settlePromise(self, state, value);
723 };
724 }
725
726 /**
727 * Link the promise1 to the promise2.
728 * @private
729 * @param {Yaku} p1
730 * @param {Yaku} p2
731 * @param {Function} onFulfilled
732 * @param {Function} onRejected
733 */
734 function addHandler (p1, p2, onFulfilled, onRejected) {
735 // 2.2.1
736 if (isFunction(onFulfilled))
737 p2._onFulfilled = onFulfilled;
738 if (isFunction(onRejected)) {
739 if (p1[$unhandled]) emitEvent($rejectionHandled, p1);
740
741 p2._onRejected = onRejected;
742 }
743
744 if (isLongStackTrace) p2._p = p1;
745 p1[p1._c++] = p2;
746
747 // 2.2.6
748 if (p1._s !== $pending)
749 scheduleHandler(p1, p2);
750
751 // 2.2.7
752 return p2;
753 }
754
755 // iterate tree
756 function hashOnRejected (node) {
757 // A node shouldn't be checked twice.
758 if (node._umark)
759 return true;
760 else
761 node._umark = true;
762
763 var i = 0
764 , len = node._c
765 , child;
766
767 while (i < len) {
768 child = node[i++];
769 if (child._onRejected || hashOnRejected(child)) return true;
770 }
771 }
772
773 function genStackInfo (reason, p) {
774 var stackInfo = [];
775
776 function push (trace) {
777 return stackInfo.push(trace.replace(/^\s+|\s+$/g, ''));
778 }
779
780 if (isLongStackTrace) {
781 if (p[$settlerTrace])
782 push(p[$settlerTrace]);
783
784 // Hope you guys could understand how the back trace works.
785 // We only have to iterate through the tree from the bottom to root.
786 (function iter (node) {
787 if (node && $promiseTrace in node) {
788 iter(node._next);
789 push(node[$promiseTrace] + '');
790 iter(node._p);
791 }
792 })(p);
793 }
794
795 return (reason && reason.stack ? reason.stack : reason) +
796 ('\n' + stackInfo.join('\n')).replace($cleanStackReg, '');
797 }
798
799 function callHanler (handler, value) {
800 // 2.2.5
801 return handler(value);
802 }
803
804 /**
805 * Resolve or reject a promise.
806 * @private
807 * @param {Yaku} p
808 * @param {Integer} state
809 * @param {Any} value
810 */
811 function settlePromise (p, state, value) {
812 var i = 0
813 , len = p._c;
814
815 // 2.1.2
816 // 2.1.3
817 if (p._s === $pending) {
818 // 2.1.1.1
819 p._s = state;
820 p._v = value;
821
822 if (state === $rejected) {
823 if (isLongStackTrace && isError(value)) {
824 value.longStack = genStackInfo(value, p);
825 }
826
827 scheduleUnhandledRejection(p);
828 }
829
830 // 2.2.4
831 while (i < len) {
832 scheduleHandler(p, p[i++]);
833 }
834 }
835
836 return p;
837 }
838
839 /**
840 * Resolve or reject promise with value x. The x can also be a thenable.
841 * @private
842 * @param {Yaku} p
843 * @param {Any | Thenable} x A normal value or a thenable.
844 */
845 function settleWithX (p, x) {
846 // 2.3.1
847 if (x === p && x) {
848 settlePromise(p, $rejected, genTypeError($promiseCircularChain));
849 return p;
850 }
851
852 // 2.3.2
853 // 2.3.3
854 if (x !== $null && (isFunction(x) || isObject(x))) {
855 // 2.3.2.1
856 var xthen = genTryCatcher(getThen)(x);
857
858 if (xthen === $tryErr) {
859 // 2.3.3.2
860 settlePromise(p, $rejected, xthen.e);
861 return p;
862 }
863
864 if (isFunction(xthen)) {
865 if (isLongStackTrace && isYaku(x))
866 p._next = x;
867
868 // Fix https://bugs.chromium.org/p/v8/issues/detail?id=4162
869 if (isYaku(x))
870 settleXthen(p, x, xthen);
871 else
872 Yaku.nextTick(function () {
873 settleXthen(p, x, xthen);
874 });
875 } else
876 // 2.3.3.4
877 settlePromise(p, $resolved, x);
878 } else
879 // 2.3.4
880 settlePromise(p, $resolved, x);
881
882 return p;
883 }
884
885 /**
886 * Try to get a promise's then method.
887 * @private
888 * @param {Thenable} x
889 * @return {Function}
890 */
891 function getThen (x) { return x.then; }
892
893 /**
894 * Resolve then with its promise.
895 * @private
896 * @param {Yaku} p
897 * @param {Thenable} x
898 * @param {Function} xthen
899 */
900 function settleXthen (p, x, xthen) {
901 // 2.3.3.3
902 var err = genTryCatcher(xthen, x)(function (y) {
903 // 2.3.3.3.3
904 // 2.3.3.3.1
905 x && (x = $null, settleWithX(p, y));
906 }, function (r) {
907 // 2.3.3.3.3
908 // 2.3.3.3.2
909 x && (x = $null, settlePromise(p, $rejected, r));
910 });
911
912 // 2.3.3.3.4.1
913 if (err === $tryErr && x) {
914 // 2.3.3.3.4.2
915 settlePromise(p, $rejected, err.e);
916 x = $null;
917 }
918 }
919
920 try {
921 module.exports = Yaku;
922 } catch (e) {
923 /* istanbul ignore next */
924 root.Yaku = Yaku;
925 }
926})();