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 | ;
|
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 | })();
|