yaku.core.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. /**
  2. * This is the minimal implementation of Yaku.
  3. * No extra helper methods.
  4. */
  5. (function () {
  6. "use strict";
  7. var $undefined
  8. , $null = null
  9. , root = typeof window === "object" ? window : global
  10. , process = root.process
  11. , Arr = Array
  12. , $rejected = 0
  13. , $resolved = 1
  14. , $pending = 2
  15. , $Symbol = "Symbol"
  16. , $iterator = "iterator"
  17. , $species = "species"
  18. , $speciesKey = $Symbol + "(" + $species + ")"
  19. , $return = "return"
  20. , $unhandled = "_uh"
  21. , $invalidThis = "Invalid this"
  22. , $invalidArgument = "Invalid argument"
  23. , $promiseCircularChain = "Chaining cycle detected for promise"
  24. , $rejectionHandled = "rejectionHandled"
  25. , $unhandledRejection = "unhandledRejection"
  26. , $tryCatchFn
  27. , $tryCatchThis
  28. , $tryErr = { e: $null }
  29. , $noop = function () {}
  30. , Symbol = root[$Symbol] || {}
  31. , nextTick = process ?
  32. process.nextTick :
  33. function (fn) { setTimeout(fn); }
  34. , speciesConstructor = function (O, D) {
  35. var C = O.constructor;
  36. return C ? (C[getSpecies()] || D) : D;
  37. }
  38. ;
  39. /**
  40. * This class follows the [Promises/A+](https://promisesaplus.com) and
  41. * [ES6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) spec
  42. * with some extra helpers.
  43. * @param {Function} executor Function object with two arguments resolve, reject.
  44. * The first argument fulfills the promise, the second argument rejects it.
  45. * We can call these functions, once our operation is completed.
  46. */
  47. var Yaku = module.exports = function Promise (executor) {
  48. var self = this,
  49. err;
  50. // "this._s" is the internal state of: pending, resolved or rejected
  51. // "this._v" is the internal value
  52. if (!isObject(self) || self._s !== $undefined)
  53. throw genTypeError($invalidThis);
  54. self._s = $pending;
  55. if (executor !== $noop) {
  56. if (!isFunction(executor))
  57. throw genTypeError($invalidArgument);
  58. err = genTryCatcher(executor)(
  59. genSettler(self, $resolved),
  60. genSettler(self, $rejected)
  61. );
  62. if (err === $tryErr)
  63. settlePromise(self, $rejected, err.e);
  64. }
  65. };
  66. Yaku["default"] = Yaku;
  67. extendPrototype(Yaku, {
  68. /**
  69. * Appends fulfillment and rejection handlers to the promise,
  70. * and returns a new promise resolving to the return value of the called handler.
  71. * @param {Function} onFulfilled Optional. Called when the Promise is resolved.
  72. * @param {Function} onRejected Optional. Called when the Promise is rejected.
  73. * @return {Yaku} It will return a new Yaku which will resolve or reject after
  74. * @example
  75. * the current Promise.
  76. * ```js
  77. * var Promise = require('yaku');
  78. * var p = Promise.resolve(10);
  79. *
  80. * p.then((v) => {
  81. * console.log(v);
  82. * });
  83. * ```
  84. */
  85. then: function then (onFulfilled, onRejected) {
  86. if (this._s === undefined) throw genTypeError();
  87. return addHandler(
  88. this,
  89. newCapablePromise(speciesConstructor(this, Yaku)),
  90. onFulfilled,
  91. onRejected
  92. );
  93. },
  94. /**
  95. * The `catch()` method returns a Promise and deals with rejected cases only.
  96. * It behaves the same as calling `Promise.prototype.then(undefined, onRejected)`.
  97. * @param {Function} onRejected A Function called when the Promise is rejected.
  98. * This function has one argument, the rejection reason.
  99. * @return {Yaku} A Promise that deals with rejected cases only.
  100. * @example
  101. * ```js
  102. * var Promise = require('yaku');
  103. * var p = Promise.reject(new Error("ERR"));
  104. *
  105. * p['catch']((v) => {
  106. * console.log(v);
  107. * });
  108. * ```
  109. */
  110. "catch": function (onRejected) {
  111. return this.then($undefined, onRejected);
  112. },
  113. // The number of current promises that attach to this Yaku instance.
  114. _pCount: 0,
  115. // The parent Yaku.
  116. _pre: $null,
  117. // A unique type flag, it helps different versions of Yaku know each other.
  118. _Yaku: 1
  119. });
  120. /**
  121. * The `Promise.resolve(value)` method returns a Promise object that is resolved with the given value.
  122. * If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable,
  123. * adopting its eventual state; otherwise the returned promise will be fulfilled with the value.
  124. * @param {Any} value Argument to be resolved by this Promise.
  125. * Can also be a Promise or a thenable to resolve.
  126. * @return {Yaku}
  127. * @example
  128. * ```js
  129. * var Promise = require('yaku');
  130. * var p = Promise.resolve(10);
  131. * ```
  132. */
  133. Yaku.resolve = function resolve (val) {
  134. return isYaku(val) ? val : settleWithX(newCapablePromise(this), val);
  135. };
  136. /**
  137. * The `Promise.reject(reason)` method returns a Promise object that is rejected with the given reason.
  138. * @param {Any} reason Reason why this Promise rejected.
  139. * @return {Yaku}
  140. * @example
  141. * ```js
  142. * var Promise = require('yaku');
  143. * var p = Promise.reject(new Error("ERR"));
  144. * ```
  145. */
  146. Yaku.reject = function reject (reason) {
  147. return settlePromise(newCapablePromise(this), $rejected, reason);
  148. };
  149. /**
  150. * The `Promise.race(iterable)` method returns a promise that resolves or rejects
  151. * as soon as one of the promises in the iterable resolves or rejects,
  152. * with the value or reason from that promise.
  153. * @param {iterable} iterable An iterable object, such as an Array.
  154. * @return {Yaku} The race function returns a Promise that is settled
  155. * the same way as the first passed promise to settle.
  156. * It resolves or rejects, whichever happens first.
  157. * @example
  158. * ```js
  159. * var Promise = require('yaku');
  160. * Promise.race([
  161. * 123,
  162. * Promise.resolve(0)
  163. * ])
  164. * .then((value) => {
  165. * console.log(value); // => 123
  166. * });
  167. * ```
  168. */
  169. Yaku.race = function race (iterable) {
  170. var self = this
  171. , p = newCapablePromise(self)
  172. , resolve = function (val) {
  173. settlePromise(p, $resolved, val);
  174. }
  175. , reject = function (val) {
  176. settlePromise(p, $rejected, val);
  177. }
  178. , ret = genTryCatcher(each)(iterable, function (v) {
  179. self.resolve(v).then(resolve, reject);
  180. });
  181. if (ret === $tryErr) return self.reject(ret.e);
  182. return p;
  183. };
  184. /**
  185. * The `Promise.all(iterable)` method returns a promise that resolves when
  186. * all of the promises in the iterable argument have resolved.
  187. *
  188. * The result is passed as an array of values from all the promises.
  189. * If something passed in the iterable array is not a promise,
  190. * it's converted to one by Promise.resolve. If any of the passed in promises rejects,
  191. * the all Promise immediately rejects with the value of the promise that rejected,
  192. * discarding all the other promises whether or not they have resolved.
  193. * @param {iterable} iterable An iterable object, such as an Array.
  194. * @return {Yaku}
  195. * @example
  196. * ```js
  197. * var Promise = require('yaku');
  198. * Promise.all([
  199. * 123,
  200. * Promise.resolve(0)
  201. * ])
  202. * .then((values) => {
  203. * console.log(values); // => [123, 0]
  204. * });
  205. * ```
  206. * @example
  207. * Use with iterable.
  208. * ```js
  209. * var Promise = require('yaku');
  210. * Promise.all((function * () {
  211. * yield 10;
  212. * yield new Promise(function (r) { setTimeout(r, 1000, "OK") });
  213. * })())
  214. * .then((values) => {
  215. * console.log(values); // => [123, 0]
  216. * });
  217. * ```
  218. */
  219. Yaku.all = function all (iterable) {
  220. var self = this
  221. , p1 = newCapablePromise(self)
  222. , res = []
  223. , ret
  224. ;
  225. function reject (reason) {
  226. settlePromise(p1, $rejected, reason);
  227. }
  228. ret = genTryCatcher(each)(iterable, function (item, i) {
  229. self.resolve(item).then(function (value) {
  230. res[i] = value;
  231. if (!--ret) settlePromise(p1, $resolved, res);
  232. }, reject);
  233. });
  234. if (ret === $tryErr) return self.reject(ret.e);
  235. if (!ret) settlePromise(p1, $resolved, []);
  236. return p1;
  237. };
  238. // To support browsers that don't support `Object.defineProperty`.
  239. genTryCatcher(function () {
  240. Object.defineProperty(Yaku, getSpecies(), {
  241. get: function () { return this; }
  242. });
  243. })();
  244. // ********************** Private **********************
  245. Yaku._Yaku = 1;
  246. /**
  247. * All static variable name will begin with `$`. Such as `$rejected`.
  248. * @private
  249. */
  250. // ******************************* Utils ********************************
  251. function getSpecies () {
  252. return Symbol[$species] || $speciesKey;
  253. }
  254. function extendPrototype (src, target) {
  255. for (var k in target) {
  256. src.prototype[k] = target[k];
  257. }
  258. return src;
  259. }
  260. function isObject (obj) {
  261. return obj && typeof obj === "object";
  262. }
  263. function isFunction (obj) {
  264. return typeof obj === "function";
  265. }
  266. function isInstanceOf (a, b) {
  267. return a instanceof b;
  268. }
  269. function ensureType (obj, fn, msg) {
  270. if (!fn(obj)) throw genTypeError(msg);
  271. }
  272. /**
  273. * Wrap a function into a try-catch.
  274. * @private
  275. * @return {Any | $tryErr}
  276. */
  277. function tryCatcher () {
  278. try {
  279. return $tryCatchFn.apply($tryCatchThis, arguments);
  280. } catch (e) {
  281. $tryErr.e = e;
  282. return $tryErr;
  283. }
  284. }
  285. /**
  286. * Generate a try-catch wrapped function.
  287. * @private
  288. * @param {Function} fn
  289. * @return {Function}
  290. */
  291. function genTryCatcher (fn, self) {
  292. $tryCatchFn = fn;
  293. $tryCatchThis = self;
  294. return tryCatcher;
  295. }
  296. /**
  297. * Generate a scheduler.
  298. * @private
  299. * @param {Integer} initQueueSize
  300. * @param {Function} fn `(Yaku, Value) ->` The schedule handler.
  301. * @return {Function} `(Yaku, Value) ->` The scheduler.
  302. */
  303. function genScheduler (initQueueSize, fn) {
  304. /**
  305. * All async promise will be scheduled in
  306. * here, so that they can be execute on the next tick.
  307. * @private
  308. */
  309. var fnQueue = Arr(initQueueSize)
  310. , fnQueueLen = 0;
  311. /**
  312. * Run all queued functions.
  313. * @private
  314. */
  315. function flush () {
  316. var i = 0;
  317. while (i < fnQueueLen) {
  318. fn(fnQueue[i], fnQueue[i + 1]);
  319. fnQueue[i++] = $undefined;
  320. fnQueue[i++] = $undefined;
  321. }
  322. fnQueueLen = 0;
  323. if (fnQueue.length > initQueueSize) fnQueue.length = initQueueSize;
  324. }
  325. return function (v, arg) {
  326. fnQueue[fnQueueLen++] = v;
  327. fnQueue[fnQueueLen++] = arg;
  328. if (fnQueueLen === 2) nextTick(flush);
  329. };
  330. }
  331. /**
  332. * Generate a iterator
  333. * @param {Any} obj
  334. * @private
  335. * @return {Object || TypeError}
  336. */
  337. function each (iterable, fn) {
  338. var len
  339. , i = 0
  340. , iter
  341. , item
  342. , ret
  343. ;
  344. if (!iterable) throw genTypeError($invalidArgument);
  345. var gen = iterable[Symbol[$iterator]];
  346. if (isFunction(gen))
  347. iter = gen.call(iterable);
  348. else if (isFunction(iterable.next)) {
  349. iter = iterable;
  350. }
  351. else if (isInstanceOf(iterable, Arr)) {
  352. len = iterable.length;
  353. while (i < len) {
  354. fn(iterable[i], i++);
  355. }
  356. return i;
  357. } else
  358. throw genTypeError($invalidArgument);
  359. while (!(item = iter.next()).done) {
  360. ret = genTryCatcher(fn)(item.value, i++);
  361. if (ret === $tryErr) {
  362. isFunction(iter[$return]) && iter[$return]();
  363. throw ret.e;
  364. }
  365. }
  366. return i;
  367. }
  368. /**
  369. * Generate type error object.
  370. * @private
  371. * @param {String} msg
  372. * @return {TypeError}
  373. */
  374. function genTypeError (msg) {
  375. return new TypeError(msg);
  376. }
  377. // *************************** Promise Helpers ****************************
  378. /**
  379. * Resolve the value returned by onFulfilled or onRejected.
  380. * @private
  381. * @param {Yaku} p1
  382. * @param {Yaku} p2
  383. */
  384. var scheduleHandler = genScheduler(999, function (p1, p2) {
  385. var x, handler;
  386. // 2.2.2
  387. // 2.2.3
  388. handler = p1._s ? p2._onFulfilled : p2._onRejected;
  389. // 2.2.7.3
  390. // 2.2.7.4
  391. if (handler === $undefined) {
  392. settlePromise(p2, p1._s, p1._v);
  393. return;
  394. }
  395. // 2.2.7.1
  396. x = genTryCatcher(callHanler)(handler, p1._v);
  397. if (x === $tryErr) {
  398. // 2.2.7.2
  399. settlePromise(p2, $rejected, x.e);
  400. return;
  401. }
  402. settleWithX(p2, x);
  403. });
  404. var scheduleUnhandledRejection = genScheduler(9, function (p) {
  405. if (!hashOnRejected(p)) {
  406. p[$unhandled] = 1;
  407. emitEvent($unhandledRejection, p);
  408. }
  409. });
  410. function emitEvent (name, p) {
  411. var browserEventName = "on" + name.toLowerCase()
  412. , browserHandler = root[browserEventName];
  413. if (process && process.listeners(name).length)
  414. name === $unhandledRejection ?
  415. process.emit(name, p._v, p) : process.emit(name, p);
  416. else if (browserHandler)
  417. browserHandler({ reason: p._v, promise: p });
  418. }
  419. function isYaku (val) { return val && val._Yaku; }
  420. function newCapablePromise (Constructor) {
  421. if (isYaku(Constructor)) return new Constructor($noop);
  422. var p, r, j;
  423. p = new Constructor(function (resolve, reject) {
  424. if (p) throw genTypeError();
  425. r = resolve;
  426. j = reject;
  427. });
  428. ensureType(r, isFunction);
  429. ensureType(j, isFunction);
  430. return p;
  431. }
  432. /**
  433. * It will produce a settlePromise function to user.
  434. * Such as the resolve and reject in this `new Yaku (resolve, reject) ->`.
  435. * @private
  436. * @param {Yaku} self
  437. * @param {Integer} state The value is one of `$pending`, `$resolved` or `$rejected`.
  438. * @return {Function} `(value) -> undefined` A resolve or reject function.
  439. */
  440. function genSettler (self, state) {
  441. return function (value) {
  442. if (state === $resolved)
  443. settleWithX(self, value);
  444. else
  445. settlePromise(self, state, value);
  446. };
  447. }
  448. /**
  449. * Link the promise1 to the promise2.
  450. * @private
  451. * @param {Yaku} p1
  452. * @param {Yaku} p2
  453. * @param {Function} onFulfilled
  454. * @param {Function} onRejected
  455. */
  456. function addHandler (p1, p2, onFulfilled, onRejected) {
  457. // 2.2.1
  458. if (isFunction(onFulfilled))
  459. p2._onFulfilled = onFulfilled;
  460. if (isFunction(onRejected)) {
  461. if (p1[$unhandled]) emitEvent($rejectionHandled, p1);
  462. p2._onRejected = onRejected;
  463. }
  464. p1[p1._pCount++] = p2;
  465. // 2.2.6
  466. if (p1._s !== $pending)
  467. scheduleHandler(p1, p2);
  468. // 2.2.7
  469. return p2;
  470. }
  471. // iterate tree
  472. function hashOnRejected (node) {
  473. // A node shouldn't be checked twice.
  474. if (node._umark)
  475. return true;
  476. else
  477. node._umark = true;
  478. var i = 0
  479. , len = node._pCount
  480. , child;
  481. while (i < len) {
  482. child = node[i++];
  483. if (child._onRejected || hashOnRejected(child)) return true;
  484. }
  485. }
  486. function callHanler (handler, value) {
  487. // 2.2.5
  488. return handler(value);
  489. }
  490. /**
  491. * Resolve or reject a promise.
  492. * @private
  493. * @param {Yaku} p
  494. * @param {Integer} state
  495. * @param {Any} value
  496. */
  497. function settlePromise (p, state, value) {
  498. var i = 0
  499. , len = p._pCount;
  500. // 2.1.2
  501. // 2.1.3
  502. if (p._s === $pending) {
  503. // 2.1.1.1
  504. p._s = state;
  505. p._v = value;
  506. if (state === $rejected) {
  507. scheduleUnhandledRejection(p);
  508. }
  509. // 2.2.4
  510. while (i < len) {
  511. scheduleHandler(p, p[i++]);
  512. }
  513. }
  514. return p;
  515. }
  516. /**
  517. * Resolve or reject promise with value x. The x can also be a thenable.
  518. * @private
  519. * @param {Yaku} p
  520. * @param {Any | Thenable} x A normal value or a thenable.
  521. */
  522. function settleWithX (p, x) {
  523. // 2.3.1
  524. if (x === p && x) {
  525. settlePromise(p, $rejected, genTypeError($promiseCircularChain));
  526. return p;
  527. }
  528. // 2.3.2
  529. // 2.3.3
  530. if (x !== $null && (isFunction(x) || isObject(x))) {
  531. // 2.3.2.1
  532. var xthen = genTryCatcher(getThen)(x);
  533. if (xthen === $tryErr) {
  534. // 2.3.3.2
  535. settlePromise(p, $rejected, xthen.e);
  536. return p;
  537. }
  538. if (isFunction(xthen)) {
  539. // Fix https://bugs.chromium.org/p/v8/issues/detail?id=4162
  540. if (isYaku(x))
  541. settleXthen(p, x, xthen);
  542. else
  543. nextTick(function () {
  544. settleXthen(p, x, xthen);
  545. });
  546. } else
  547. // 2.3.3.4
  548. settlePromise(p, $resolved, x);
  549. } else
  550. // 2.3.4
  551. settlePromise(p, $resolved, x);
  552. return p;
  553. }
  554. /**
  555. * Try to get a promise's then method.
  556. * @private
  557. * @param {Thenable} x
  558. * @return {Function}
  559. */
  560. function getThen (x) { return x.then; }
  561. /**
  562. * Resolve then with its promise.
  563. * @private
  564. * @param {Yaku} p
  565. * @param {Thenable} x
  566. * @param {Function} xthen
  567. */
  568. function settleXthen (p, x, xthen) {
  569. // 2.3.3.3
  570. var err = genTryCatcher(xthen, x)(function (y) {
  571. // 2.3.3.3.3
  572. // 2.3.3.3.1
  573. x && (x = $null, settleWithX(p, y));
  574. }, function (r) {
  575. // 2.3.3.3.3
  576. // 2.3.3.3.2
  577. x && (x = $null, settlePromise(p, $rejected, r));
  578. });
  579. // 2.3.3.3.4.1
  580. if (err === $tryErr && x) {
  581. // 2.3.3.3.4.2
  582. settlePromise(p, $rejected, err.e);
  583. x = $null;
  584. }
  585. }
  586. })();