├── .npmignore ├── benchmark ├── q.js ├── yaku.js ├── bluebird.js ├── es6-promise.js ├── native.js ├── others │ ├── object-vs-array.js │ ├── utils.js │ ├── call-vs-this.js │ ├── closure-perf.js │ ├── try-catch.js │ ├── browser.html │ ├── this-vs-closure.js │ └── closure-async.js ├── template.js └── readme.md ├── .gitignore ├── .travis.yml ├── src ├── throw.js ├── never.js ├── isPromise.js ├── global.js ├── sleep.js ├── browser.full.js ├── Deferred.js ├── _.js ├── any.js ├── retry.js ├── flow.js ├── callbackify.js ├── genIterator.js ├── promisify.js ├── async.js ├── Observable.js ├── utils.js └── yaku.js ├── test ├── browser.html ├── test-browser.js ├── lab.js ├── webpack.js ├── memory.js ├── promises-aplus-tests.js ├── testSuit.js ├── promises-es6-tests.js ├── unhandledRejection.js └── basic.js ├── webpack.config.js ├── LICENSE ├── package.json ├── docs ├── changelog.md ├── lazyTree.md ├── debugHelperComparison.md ├── minPromiseA+.coffee └── readme.jst.md ├── nofile.js └── readme.md /.npmignore: -------------------------------------------------------------------------------- 1 | lib/all.js 2 | lib/test-basic.js -------------------------------------------------------------------------------- /benchmark/q.js: -------------------------------------------------------------------------------- 1 | require("./template")("q", require("q").Promise); 2 | -------------------------------------------------------------------------------- /benchmark/yaku.js: -------------------------------------------------------------------------------- 1 | require("./template")("yaku", require("../src/yaku")); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | dist 3 | node_modules 4 | *.sublime-* 5 | .nokit 6 | .nobone 7 | -------------------------------------------------------------------------------- /benchmark/bluebird.js: -------------------------------------------------------------------------------- 1 | require("./template")("bluebird", require("bluebird")); 2 | -------------------------------------------------------------------------------- /benchmark/es6-promise.js: -------------------------------------------------------------------------------- 1 | require("./template")("es6-promise", require("es6-promise").Promise); 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | - "0.10" 5 | 6 | branches: 7 | only: 8 | - master 9 | -------------------------------------------------------------------------------- /src/throw.js: -------------------------------------------------------------------------------- 1 | module.exports = function (err) { 2 | setTimeout(function () { 3 | throw err; 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /src/never.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | 3 | module.exports = function () { 4 | return new _.Promise(function () {}); 5 | }; 6 | -------------------------------------------------------------------------------- /benchmark/native.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise !== "undefined" && Promise !== null) { 2 | require("./template")("native", Promise); 3 | } 4 | -------------------------------------------------------------------------------- /src/isPromise.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | 3 | module.exports = function (obj) { 4 | return obj && _.isFunction(obj.then); 5 | }; 6 | -------------------------------------------------------------------------------- /test/browser.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | var Yaku = require("./yaku"); 2 | 3 | try { 4 | global.Promise = Yaku; 5 | window.Promise = Yaku; 6 | } catch (err) { 7 | null; 8 | } -------------------------------------------------------------------------------- /test/test-browser.js: -------------------------------------------------------------------------------- 1 | var junit = require("junit"); 2 | 3 | var it = junit(); 4 | 5 | require("./basic")(it); 6 | require("./unhandledRejection")(it).then(it.run); 7 | -------------------------------------------------------------------------------- /test/lab.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable */ 2 | 3 | var Yaku = require("../src/yaku"); 4 | var utils = require("../src/utils"); 5 | 6 | var one = new utils.Observable(); 7 | 8 | var two = new utils.Observable(); -------------------------------------------------------------------------------- /src/sleep.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | 3 | module.exports = function (time, val) { 4 | return new _.Promise(function (r) { 5 | return setTimeout((function () { 6 | return r(val); 7 | }), time); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /test/webpack.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Promise = require("../lib/yaku.js"); 4 | 5 | new Promise(function (resolve) { 6 | setTimeout(function () { 7 | resolve(); 8 | }); 9 | }).then(function () { 10 | console.log("done"); 11 | }); 12 | -------------------------------------------------------------------------------- /src/browser.full.js: -------------------------------------------------------------------------------- 1 | // This file is intended for browser only. 2 | 3 | var Yaku = require("./yaku"); 4 | 5 | var utils = require("./utils"); 6 | 7 | for (var key in utils) { 8 | Yaku[key] = utils[key]; 9 | } 10 | 11 | module.exports = window.Yaku = Yaku; 12 | -------------------------------------------------------------------------------- /src/Deferred.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | 3 | module.exports = function () { 4 | var defer; 5 | defer = {}; 6 | defer.promise = new _.Promise(function (resolve, reject) { 7 | defer.resolve = resolve; 8 | return defer.reject = reject; 9 | }); 10 | return defer; 11 | }; 12 | -------------------------------------------------------------------------------- /benchmark/others/object-vs-array.js: -------------------------------------------------------------------------------- 1 | var utils; 2 | 3 | utils = require("./utils"); 4 | 5 | console.log(utils.run(3, function () { 6 | return [1, 2, 3, 4]; 7 | })); 8 | 9 | console.log(utils.run(3, function () { 10 | return { 11 | a: 1, 12 | b: 2, 13 | c: 3, 14 | d: 4 15 | }; 16 | })); 17 | -------------------------------------------------------------------------------- /benchmark/others/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | run: function (span, fn) { 3 | var count, start; 4 | span = span * 1000; 5 | start = Date.now(); 6 | count = 0; 7 | while (Date.now() - start < span) { 8 | fn(); 9 | count++; 10 | } 11 | return count; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /benchmark/others/call-vs-this.js: -------------------------------------------------------------------------------- 1 | var bar, foo, obj, utils; 2 | 3 | utils = require("./utils"); 4 | 5 | foo = function () { 6 | return this.count++; 7 | }; 8 | 9 | bar = function (self) { 10 | return self.count++; 11 | }; 12 | 13 | obj = { 14 | count: 0 15 | }; 16 | 17 | utils.run(3, function () { 18 | return foo.call(obj); 19 | }); 20 | 21 | console.log(obj.count); 22 | 23 | obj = { 24 | count: 0 25 | }; 26 | 27 | utils.run(3, function () { 28 | return bar(obj); 29 | }); 30 | 31 | console.log(obj.count); 32 | -------------------------------------------------------------------------------- /test/memory.js: -------------------------------------------------------------------------------- 1 | var Promise, count, p; 2 | 3 | Promise = require("../src/yaku"); 4 | 5 | p = Promise.resolve(); 6 | 7 | count = 0; 8 | 9 | setInterval(function () { 10 | return p = p.then(function () { 11 | return new Promise(function (r) { 12 | return setTimeout(function () { 13 | count++; 14 | return r({ 15 | data: "ok" 16 | }); 17 | }); 18 | }); 19 | }); 20 | }); 21 | 22 | setInterval(function () { 23 | return console.log(count, process.memoryUsage()); 24 | }, 1000); 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var isProduction = process.env.NODE_ENV === "production"; 3 | 4 | var self = module.exports = { 5 | entry: { 6 | "test-browser": "./test/test-browser.js", 7 | "yaku.browser.full": "./src/browser.full" 8 | }, 9 | 10 | output: { 11 | filename: "[name].js", 12 | path: "./dist", 13 | pathinfo: true 14 | }, 15 | 16 | debug: true 17 | }; 18 | 19 | if (isProduction) { 20 | self.output.filename = "[name].min.js"; 21 | self.plugins = [new webpack.optimize.UglifyJsPlugin()]; 22 | } 23 | -------------------------------------------------------------------------------- /src/_.js: -------------------------------------------------------------------------------- 1 | var Promise = require("./yaku"); 2 | 3 | module.exports = { 4 | 5 | extendPrototype: function (src, target) { 6 | for (var k in target) { 7 | src.prototype[k] = target[k]; 8 | } 9 | return src; 10 | }, 11 | 12 | isArray: function (obj) { 13 | return obj instanceof Array; 14 | }, 15 | 16 | isFunction: function (obj) { 17 | return typeof obj === "function"; 18 | }, 19 | 20 | isNumber: function (obj) { 21 | return typeof obj === "number"; 22 | }, 23 | 24 | Promise: Promise, 25 | 26 | slice: [].slice 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /test/promises-aplus-tests.js: -------------------------------------------------------------------------------- 1 | var promisesAplusTests = require("promises-aplus-tests"); 2 | var kit = require("nokit"); 3 | 4 | var Promise = require("../src/yaku"); 5 | 6 | module.exports = function (opts) { 7 | var adapter = { 8 | deferred: function () { 9 | var defer; 10 | defer = {}; 11 | defer.promise = new Promise(function (resolve, reject) { 12 | defer.resolve = resolve; 13 | return defer.reject = reject; 14 | }); 15 | return defer; 16 | } 17 | }; 18 | 19 | return kit.promisify(promisesAplusTests)(adapter, opts); 20 | }; 21 | -------------------------------------------------------------------------------- /src/any.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | var genIterator = require("./genIterator"); 3 | 4 | module.exports = function (iterable) { 5 | var iter = genIterator(iterable); 6 | 7 | return new _.Promise(function (resolve, reject) { 8 | var countDown = 0 9 | , reasons = [] 10 | , item; 11 | 12 | function onError (reason) { 13 | reasons.push(reason); 14 | if (!--countDown) 15 | reject(reasons); 16 | } 17 | 18 | while (!(item = iter.next()).done) { 19 | countDown++; 20 | _.Promise.resolve(item.value).then(resolve, onError); 21 | } 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /test/testSuit.js: -------------------------------------------------------------------------------- 1 | 2 | // Keep the native out of the place. 3 | var root = typeof global === "object" ? global : window; 4 | root.Promise = null; 5 | 6 | var Yaku = require("../src/yaku"); 7 | 8 | module.exports = function (title, fn) { 9 | return function (it) { 10 | return it.describe(title, function (it) { 11 | return fn(function (msg, expected, test) { 12 | return it(msg, function () { 13 | return Yaku.resolve(test()).then(function (actual) { 14 | return it.eq(expected, actual); 15 | }); 16 | }); 17 | }); 18 | }); 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /benchmark/others/closure-perf.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | As a result execute closure at runtime is very expensive. 5 | 6 | foo: 269ms 7 | bar: 7ms 8 | */ 9 | var bar, foo, foo_, test; 10 | 11 | foo = function () { 12 | (function () { 13 | 1 + 2; 14 | })(); 15 | }; 16 | 17 | foo_ = function () { 18 | var s = function () { 19 | 1 + 2; 20 | }; 21 | s(); 22 | 1 + 2; 23 | }; 24 | 25 | bar = function () { 26 | 1 + 2; 27 | }; 28 | 29 | test = function (name, fn) { 30 | var countDown; 31 | countDown = Math.pow(10, 6); 32 | console.time(name); 33 | while (countDown--) { 34 | fn(); 35 | } 36 | return console.timeEnd(name); 37 | }; 38 | 39 | test("foo", foo); 40 | 41 | test("foo_", foo_); 42 | 43 | test("bar", bar); 44 | -------------------------------------------------------------------------------- /src/retry.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | var $retryError = {}; 3 | 4 | module.exports = function (retries, fn, self) { 5 | var errs = [], args; 6 | var countdown = _.isFunction(retries) ? 7 | retries : function () { return retries--; }; 8 | 9 | function tryFn (isContinue) { 10 | return isContinue ? fn.apply(self, args) : _.Promise.reject($retryError); 11 | } 12 | 13 | function onError (err) { 14 | if (err === $retryError) return _.Promise.reject(errs); 15 | 16 | errs.push(err); 17 | return attempt(); 18 | } 19 | 20 | function attempt () { 21 | if (args === void 0) args = arguments; 22 | return _.Promise.resolve(countdown(errs)).then(tryFn).catch(onError); 23 | } 24 | 25 | return attempt; 26 | }; 27 | -------------------------------------------------------------------------------- /benchmark/others/try-catch.js: -------------------------------------------------------------------------------- 1 | var test, test2; 2 | 3 | test = function () { 4 | var count, foo, start; 5 | count = 0; 6 | foo = function () { 7 | return count++; 8 | }; 9 | start = Date.now(); 10 | while (Date.now() - start < 1000 * 2) { 11 | foo(); 12 | } 13 | return console.log(count); 14 | }; 15 | 16 | test2 = function () { 17 | var count, foo, start; 18 | try { 19 | count = 0; 20 | foo = function () { 21 | return count++; 22 | }; 23 | start = Date.now(); 24 | while (Date.now() - start < 1000 * 2) { 25 | foo(); 26 | } 27 | return console.log(count); 28 | } catch (undefined) { null; } 29 | }; 30 | 31 | test(); 32 | 33 | try { 34 | test(); 35 | } catch (undefined) { null; } 36 | 37 | test2(); 38 | -------------------------------------------------------------------------------- /src/flow.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | var genIterator = require("./genIterator"); 3 | var isPromise = require("./isPromise"); 4 | 5 | module.exports = function (iterable) { 6 | var iter = genIterator(iterable); 7 | 8 | return function (val) { 9 | function run (pre) { 10 | return pre.then(function (val) { 11 | var task = iter.next(val); 12 | 13 | if (task.done) { 14 | return val; 15 | } 16 | var curr = task.value; 17 | return run( 18 | isPromise(curr) ? curr : 19 | _.isFunction(curr) ? _.Promise.resolve(curr(val)) : 20 | _.Promise.resolve(curr) 21 | ); 22 | }); 23 | } 24 | 25 | return run(_.Promise.resolve(val)); 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /benchmark/others/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
4 |
5 |
6 | # Overview
7 |
8 | Yaku is full compatible with ES6's native [Promise][native], but much faster, and more error friendly.
9 | If you want to learn how Promise works, read the minimum implementation [docs/minPromiseA+.coffee][]. Without comments, it is only 80 lines of code (gzipped size is 0.5KB).
10 | It only implements the `constructor` and `then`. It passed all the tests of [promises-aplus-tests][].
11 |
12 | I am not an optimization freak, I try to keep the source code readable and maintainable.
13 | Premature optimization is the root of all evil. I write this lib to research one of my data structure
14 | ideas: [docs/lazyTree.md][].
15 |
16 | [](http://badge.fury.io/js/yaku) [](https://travis-ci.org/ysmood/yaku) [](https://david-dm.org/ysmood/yaku)
17 |
18 |
19 |
20 | # Features
21 |
22 | - The minified file is only <%= doc.size %>KB (1.5KB gzipped) ([Bluebird][] / 73KB, [ES6-promise][] / 18KB)
23 | - [Better "possibly unhandled rejection" and "long stack trace"][docs/debugHelperComparison.md] than [Bluebird][]
24 | - Much better performance than the native Promise
25 | - 100% compliant with Promises/A+ specs and ES6
26 | - Designed to work on IE5+ and other major browsers
27 | - Well commented source code with every Promises/A+ spec
28 |
29 |
30 |
31 | # Quick Start
32 |
33 | ## Node.js
34 |
35 | ```shell
36 | npm install yaku
37 | ```
38 |
39 | Then:
40 | ```js
41 | var Promise = require('yaku');
42 | ```
43 |
44 |
45 |
46 | ## Browser
47 |
48 | Use something like [Browserify][] or [Webpack][], or download the `yaku.js` file from [release page][].
49 | Raw usage without:
50 |
51 | ```html
52 |
53 |
57 | ```
58 |
59 |
60 |
61 | # Change Log
62 |
63 | [docs/changelog.md](docs/changelog.md)
64 |
65 |
66 |
67 | # Compare to Other Promise Libs
68 |
69 | These comparisons only reflect some limited truth, no one is better than all others on all aspects.
70 | For more details see the [benchmark/readme.md](benchmark/readme.md). There are tons of Promises/A+ implementations, you can see them [here](https://promisesaplus.com/implementations). Only some of the famous ones were tested.
71 |
72 | | Name | 1ms async task / mem | sync task / mem | Helpers | file size |
73 | | -------------------- | -------------------- | --------------- | ------- | --------- |
74 | | Yaku | 257ms / 110MB | 126ms / 80MB | +++ | <%= doc.size %>KB |
75 | | [Bluebird][] v2.9 | 249ms / 102MB | 155ms / 80MB | +++++++ | 73KB |
76 | | [ES6-promise][] v2.3 | 427ms / 120MB | 92ms / 78MB | + | 18KB |
77 | | [native][] iojs v1.8 | 789ms / 189MB | 605ms / 147MB | + | 0KB |
78 | | [q][] v1.3 | 2648ms / 646MB | 2373ms / 580MB | +++ | 24K |
79 |
80 | - **Helpers**: extra methods that help with your promise programming, such as
81 | async flow control helpers, debug helpers. For more details: [docs/debugHelperComparison.md][].
82 | - **1ms async task**: `npm run no -- benchmark`, the smaller the better.
83 | - **sync task**: `npm run no -- benchmark --sync`, the smaller the better.
84 |
85 |
86 |
87 | # FAQ
88 |
89 | - `catch` on old brwoser (IE7, IE8 etc)?
90 |
91 | > In ECMA-262 spec, `catch` cannot be used as method name. You have to alias the method name or use something like `Promise.resolve()['catch'](function() {})` or `Promise.resolve().then(null, function() {})`.
92 |
93 | - Will Yaku implement `done`, `finally`, `promisify`, etc?
94 |
95 | > No. All non-ES6 APIs are only implemented for debugging and testing, which means when you remove Yaku, everything
96 | > should work well with ES6 native promise. If you need fancy and magic, go for [Bluebird][].
97 |
98 | - When using with Babel and Regenerator, the unhandled rejection doesn't work.
99 |
100 | > Because Regenerator use global Promise directly and don't have an api to set the Promise lib.
101 | > You have to import Yaku globally to make it use Yaku: `require("yaku/lib/global");`.
102 |
103 | - Better long stack trace support?
104 |
105 | > Latest Node.js and browsers are already support it. If you enabled it, Yaku will take advantage of it
106 | > without much overhead. Such as this library [longjohn][] for Node.js, or this article for [Chrome][crhome-lst].
107 |
108 | - The name Yaku is weird?
109 |
110 | > The name `yaku` comes from the word `約束(yakusoku)` which means promise.
111 |
112 |
113 | # Unhandled Rejection
114 |
115 | Yaku will report any unhandled rejection via `console.error` by default, in case you forget to write `catch`.
116 | You can catch with them manually:
117 |
118 | - Browser: `window.onunhandledrejection = ({ promise, reason }) => { /* Your Code */ };`
119 | - Node: `process.on("unhandledRejection", (reason, promise) => { /* Your Code */ });`
120 |
121 | For more spec read [Unhandled Rejection Tracking Browser Events](https://github.com/domenic/unhandled-rejections-browser-spec).
122 |
123 |
124 | # API
125 |
126 | <%= doc['src/yaku.js'] %>
127 |
128 |
129 |
130 | # Utils
131 |
132 | It's a bundle of all the following functions. You can require them all with `var yutils = require("yaku/lib/utils")`,
133 | or require them separately like `require("yaku/lib/flow")`. If you want to use it in the browser, you have to use `browserify` or `webpack`. You can even use another Promise lib, such as:
134 |
135 | ```js
136 | require("yaku/lib/_").Promise = require("bluebird");
137 | var source = require("yaku/lib/source");
138 |
139 | // now "source" use bluebird instead of yaku.
140 | ```
141 |
142 | <%= doc['src/utils.js'] %>
143 |
144 |
145 | # Observable
146 |
147 | <%= doc['src/Observable.js'] %>
148 |
149 |
150 |
151 | # Unit Test
152 |
153 | This project use [promises-aplus-tests][] to test the compliance of Promises/A+ specification. There are about 900 test cases.
154 |
155 | Use `npm run no -- test` to run the unit test.
156 |
157 |
158 |
159 | # Benchmark
160 |
161 | Use `npm run no -- benchmark` to run the benchmark.
162 |
163 |
164 |
165 | # Contribute
166 |
167 | Other than use `gulp`, all my projects use [nokit][] to deal with automation.
168 | Run `npm run no -- -h` to print all the tasks that defined in the [nofile.js][].
169 | If you installed `nokit` globally, you can just run `no -h` without `npm run` and `--`.
170 |
171 |
172 | [docs/lazyTree.md]: docs/lazyTree.md
173 | [docs/debugHelperComparison.md]: docs/debugHelperComparison.md
174 | [Bluebird]: https://github.com/petkaantonov/bluebird
175 | [ES6-promise]: https://github.com/jakearchibald/es6-promise
176 | [native]: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects
177 | [q]: https://github.com/kriskowal/q
178 | [release page]: https://github.com/ysmood/yaku/releases
179 | [docs/minPromiseA+.coffee]: docs/minPromiseA+.coffee
180 | [promises-aplus-tests]: https://github.com/promises-aplus/promises-tests
181 | [longjohn]: https://github.com/mattinsler/longjohn
182 | [crhome-lst]: http://www.html5rocks.com/en/tutorials/developertools/async-call-stack
183 | [Browserify]: http://browserify.org
184 | [Webpack]: http://webpack.github.io/
185 | [CoffeeScript]: http://coffeescript.org/
186 | [nokit]: https://github.com/ysmood/nokit
187 | [nofile.js]: nofile.js
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | // This file contains all the non-ES6-standard helpers based on promise.
2 |
3 | module.exports = {
4 |
5 | /**
6 | * Similar with the `Promise.race`, but only rejects when every entry rejects.
7 | * @param {iterable} iterable An iterable object, such as an Array.
8 | * @return {Yaku}
9 | * @example
10 | * ```js
11 | * var any = require('yaku/lib/any');
12 | * any([
13 | * 123,
14 | * Promise.resolve(0),
15 | * Promise.reject(new Error("ERR"))
16 | * ])
17 | * .then((value) => {
18 | * console.log(value); // => 123
19 | * });
20 | * ```
21 | */
22 | any: require("./any"),
23 |
24 | /**
25 | * A function that helps run functions under a concurrent limitation.
26 | * To run functions sequentially, use `yaku/lib/flow`.
27 | * @param {Int} limit The max task to run at a time. It's optional.
28 | * Default is `Infinity`.
29 | * @param {Iterable} list Any [iterable](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) object. It should be a lazy iteralbe object,
30 | * don't pass in a normal Array with promises.
31 | * @param {Boolean} saveResults Whether to save each promise's result or
32 | * not. Default is true.
33 | * @param {Function} progress If a task ends, the resolved value will be
34 | * passed to this function.
35 | * @return {Promise}
36 | * @example
37 | * ```js
38 | * var kit = require('nokit');
39 | * var async = require('yaku/lib/async');
40 | *
41 | * var urls = [
42 | * 'http://a.com',
43 | * 'http://b.com',
44 | * 'http://c.com',
45 | * 'http://d.com'
46 | * ];
47 | * var tasks = function * () {
48 | * var i = 0;
49 | * yield kit.request(url[i++]);
50 | * yield kit.request(url[i++]);
51 | * yield kit.request(url[i++]);
52 | * yield kit.request(url[i++]);
53 | * }();
54 | *
55 | * async(tasks).then(() => kit.log('all done!'));
56 | *
57 | * async(2, tasks).then(() => kit.log('max concurrent limit is 2'));
58 | *
59 | * async(3, { next: () => {
60 | * var url = urls.pop();
61 | * return {
62 | * done: !url,
63 | * value: url && kit.request(url)
64 | * };
65 | * } })
66 | * .then(() => kit.log('all done!'));
67 | * ```
68 | */
69 | async: require("./async"),
70 |
71 | /**
72 | * If a function returns promise, convert it to
73 | * node callback style function.
74 | * @param {Function} fn
75 | * @param {Any} self The `this` to bind to the fn.
76 | * @return {Function}
77 | */
78 | callbackify: require("./callbackify"),
79 |
80 | /**
81 | * **deprecate** Create a `jQuery.Deferred` like object.
82 | * It will cause some buggy problems, please don't use it.
83 | */
84 | Deferred: require("./Deferred"),
85 |
86 | /**
87 | * Creates a function that is the composition of the provided functions.
88 | * See `yaku/lib/async`, if you need concurrent support.
89 | * @param {Iterable} list Any [iterable](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) object. It should be a lazy iteralbe object,
90 | * don't pass in a normal Array with promises.
91 | * @return {Function} `(val) -> Promise` A function that will return a promise.
92 | * @example
93 | * It helps to decouple sequential pipeline code logic.
94 | * ```js
95 | * var kit = require('nokit');
96 | * var flow = require('yaku/lib/flow');
97 | *
98 | * function createUrl (name) {
99 | * return "http://test.com/" + name;
100 | * }
101 | *
102 | * function curl (url) {
103 | * return kit.request(url).then((body) => {
104 | * kit.log('get');
105 | * return body;
106 | * });
107 | * }
108 | *
109 | * function save (str) {
110 | * kit.outputFile('a.txt', str).then(() => {
111 | * kit.log('saved');
112 | * });
113 | * }
114 | *
115 | * var download = flow(createUrl, curl, save);
116 | * // same as "download = flow([createUrl, curl, save])"
117 | *
118 | * download('home');
119 | * ```
120 | * @example
121 | * Walk through first link of each page.
122 | * ```js
123 | * var kit = require('nokit');
124 | * var flow = require('yaku/lib/flow');
125 | *
126 | * var list = [];
127 | * function iter (url) {
128 | * return {
129 | * done: !url,
130 | * value: url && kit.request(url).then((body) => {
131 | * list.push(body);
132 | * var m = body.match(/href="(.+?)"/);
133 | * if (m) return m[0];
134 | * });
135 | * };
136 | * }
137 | *
138 | * var walker = flow(iter);
139 | * walker('test.com');
140 | * ```
141 | */
142 | flow: require("./flow"),
143 |
144 | /**
145 | * **deprecate** Check if an object is a promise-like object.
146 | * Don't use it to coercive a value to Promise, instead use `Promise.resolve`.
147 | * @param {Any} obj
148 | * @return {Boolean}
149 | */
150 | isPromise: require("./isPromise"),
151 |
152 | /**
153 | * Create a promise that never ends.
154 | * @return {Promise} A promise that will end the current pipeline.
155 | */
156 | never: require("./never"),
157 |
158 | /**
159 | * Convert a node callback style function to a function that returns
160 | * promise when the last callback is not supplied.
161 | * @param {Function} fn
162 | * @param {Any} self The `this` to bind to the fn.
163 | * @return {Function}
164 | * @example
165 | * ```js
166 | * var promisify = require('yaku/lib/promisify');
167 | * function foo (val, cb) {
168 | * setTimeout(() => {
169 | * cb(null, val + 1);
170 | * });
171 | * }
172 | *
173 | * var bar = promisify(foo);
174 | *
175 | * bar(0).then((val) => {
176 | * console.log val // output => 1
177 | * });
178 | *
179 | * // It also supports the callback style.
180 | * bar(0, (err, val) => {
181 | * console.log(val); // output => 1
182 | * });
183 | * ```
184 | */
185 | promisify: require("./promisify"),
186 |
187 | /**
188 | * Create a promise that will wait for a while before resolution.
189 | * @param {Integer} time The unit is millisecond.
190 | * @param {Any} val What the value this promise will resolve.
191 | * @return {Promise}
192 | * @example
193 | * ```js
194 | * var sleep = require('yaku/lib/sleep');
195 | * sleep(1000).then(() => console.log('after one second'));
196 | * ```
197 | */
198 | sleep: require("./sleep"),
199 |
200 | /**
201 | * Read the `Observable` section.
202 | * @type {Function}
203 | */
204 | Observable: require("./Observable"),
205 |
206 | /**
207 | * Retry a function until it resolves before a mount of times, or reject with all
208 | * the error states.
209 | * @version_added v0.7.10
210 | * @param {Number | Function} countdown How many times to retry before rejection.
211 | * When it's a function `(errs) => Boolean | Promise.resolve(Boolean)`,
212 | * you can use it to create complex countdown logic,
213 | * it can even return a promise to create async countdown logic.
214 | * @param {Function} fn The function can return a promise or not.
215 | * @param {Any} this Optional. The context to call the function.
216 | * @return {Function} The wrapped function. The function will reject an array
217 | * of reasons that throwed by each try.
218 | * @example
219 | * Retry 3 times before rejection.
220 | * ```js
221 | * var retry = require('yaku/lib/retry');
222 | * var { request } = require('nokit');
223 | *
224 | * retry(3, request)('http://test.com').then(
225 | * (body) => console.log(body),
226 | * (errs) => console.error(errs)
227 | * );
228 | * ```
229 | * @example
230 | * Here a more complex retry usage, it shows an random exponential backoff algorithm to
231 | * wait and retry again, which means the 10th attempt may take 10 minutes to happen.
232 | * ```js
233 | * var retry = require('yaku/lib/retry');
234 | * var sleep = require('yaku/lib/sleep');
235 | * var { request } = require('nokit');
236 | *
237 | * function countdown (retries) {
238 | * var attempt = 0;
239 | * return async () => {
240 | * var r = Math.random() * Math.pow(2, attempt) * 1000;
241 | * var t = Math.min(r, 1000 * 60 * 10);
242 | * await sleep(t);
243 | * return attempt++ < retries;
244 | * };
245 | * }
246 | *
247 | * retry(countdown(10), request)('http://test.com').then(
248 | * (body) => console.log(body),
249 | * (errs) => console.error(errs)
250 | * );
251 | * ```
252 | */
253 | retry: require("./retry"),
254 |
255 | /**
256 | * Throw an error to break the program.
257 | * @param {Any} err
258 | * @example
259 | * ```js
260 | * var ythrow = require('yaku/lib/throw');
261 | * Promise.resolve().then(() => {
262 | * // This error won't be caught by promise.
263 | * ythrow('break the program!');
264 | * });
265 | * ```
266 | */
267 | "throw": require("./throw")
268 | };
269 |
--------------------------------------------------------------------------------
/test/basic.js:
--------------------------------------------------------------------------------
1 |
2 | var Yaku = require("../src/yaku");
3 | var utils = require("../src/utils");
4 | var testSuit = require("./testSuit");
5 |
6 | var $val = {
7 | val: "ok"
8 | };
9 |
10 | module.exports = testSuit("basic", function (it) { return [
11 |
12 | it("resolve", $val, function () {
13 | return new Yaku(function (resolve) {
14 | return resolve($val);
15 | });
16 | }),
17 |
18 | it("resolve promise like value", $val, function () {
19 | return new Yaku(function (resolve) {
20 | return resolve({
21 | then: function (fulfil) {
22 | return fulfil($val);
23 | }
24 | });
25 | });
26 | }),
27 |
28 | it("constructor abort", 111, function () {
29 | var p;
30 | p = new Yaku(function (resolve, reject) {
31 | var tmr;
32 | tmr = setTimeout(resolve, 100, "done");
33 | return this.abort = function () {
34 | clearTimeout(tmr);
35 | return reject(111);
36 | };
37 | });
38 | p.abort($val);
39 | return p["catch"](function (e) {
40 | return e;
41 | });
42 | }),
43 |
44 | it("constructor throw", $val, function () {
45 | return new Yaku(function () {
46 | throw $val;
47 | })["catch"](function (e) {
48 | return e;
49 | });
50 | }),
51 |
52 | it("resolve static", $val, function () {
53 | return Yaku.resolve($val);
54 | }),
55 |
56 | it("resolve promise", $val, function () {
57 | return Yaku.resolve(Yaku.resolve($val));
58 | }),
59 |
60 | it("reject", $val, function () {
61 | return Yaku.reject($val)["catch"](function (val) {
62 | return val;
63 | });
64 | }),
65 |
66 | it("catch", $val, function () {
67 | return new Yaku(function (nil, reject) {
68 | return reject($val);
69 | })["catch"](function (val) {
70 | return val;
71 | });
72 | }),
73 |
74 | it("chain", "ok", function () {
75 | return Yaku.resolve().then(function () {
76 | return new Yaku(function (r) {
77 | return setTimeout(function () {
78 | return r("ok");
79 | }, 10);
80 | });
81 | });
82 | }),
83 |
84 | it("empty all", [], function () {
85 | return Yaku.all([]);
86 | }),
87 |
88 | it("all", [1, "test", "x", 10, 0], function () {
89 | function randomPromise (i) {
90 | return new Yaku(function (r) {
91 | return setTimeout(function () {
92 | return r(i);
93 | }, Math.random() * 10);
94 | });
95 | }
96 |
97 | return Yaku.all([
98 | randomPromise(1), randomPromise("test"), Yaku.resolve("x"), new Yaku(function (r) {
99 | return setTimeout(function () {
100 | return r(10);
101 | }, 1);
102 | }), new Yaku(function (r) {
103 | return r(0);
104 | })
105 | ]);
106 | }),
107 |
108 | it("race", 0, function () {
109 | return Yaku.race([
110 | new Yaku(function (r) {
111 | return setTimeout(function () {
112 | return r(1);
113 | }, 10);
114 | }),
115 | new Yaku(function (r) {
116 | return setTimeout(function () {
117 | return r(0);
118 | });
119 | })
120 | ]);
121 | }),
122 |
123 | it("any one resolved", 0, function () {
124 | return utils.any([
125 | Yaku.reject(1),
126 | Yaku.resolve(0)
127 | ]);
128 | }),
129 |
130 | it("any all rejected", [0, 1], function () {
131 | return utils.any([
132 | Yaku.reject(0),
133 | Yaku.reject(1)
134 | ]).catch(function (v) {
135 | return v;
136 | });
137 | }),
138 |
139 | it("async array", [0, null, void 0, 1, 2, 3], function () {
140 | var list;
141 | list = [
142 | 0,
143 | null,
144 | void 0,
145 | utils.sleep(20, 1),
146 | utils.sleep(10, 2),
147 | utils.sleep(10, 3)
148 | ];
149 | return utils.async(2, list);
150 | }),
151 |
152 | it("async error", $val, function () {
153 | var iter = {
154 | i: 0,
155 | next: function () {
156 | var fn = [
157 | function () {
158 | return utils.sleep(10, 1);
159 | }, function () {
160 | throw $val;
161 | }, function () {
162 | return utils.sleep(10, 3);
163 | }
164 | ][iter.i++];
165 |
166 | return { done: !fn, value: fn && fn() };
167 | }
168 | };
169 | return utils.async(2, iter)["catch"](function (err) {
170 | return err;
171 | });
172 | }),
173 |
174 | it("async iter progress", 10, function () {
175 | var iter = { i: 0, next: function () {
176 | var done = iter.i++ >= 10;
177 | return {
178 | done: done,
179 | value: !done && new Yaku(function (r) {
180 | return setTimeout((function () {
181 | return r(1);
182 | }), 1);
183 | })
184 | };
185 | } };
186 |
187 | var count = 0;
188 | return utils.async(3, iter, false, function (ret) {
189 | return count += ret;
190 | }).then(function () {
191 | return count;
192 | });
193 | }),
194 |
195 | it("flow array", "bc", function () {
196 | return (utils.flow([
197 | "a", Yaku.resolve("b"), function (v) {
198 | return v + "c";
199 | }
200 | ]))(0);
201 | }),
202 |
203 | it("flow error", $val, function () {
204 | return (utils.flow([
205 | "a", Yaku.resolve("b"), function () {
206 | throw $val;
207 | }
208 | ]))(0)["catch"](function (err) {
209 | return err;
210 | });
211 | }),
212 |
213 | it("flow iter", [0, 1, 2, 3], function () {
214 | var list;
215 | list = [];
216 | return utils.flow({ next: function (v) {
217 | return {
218 | done: v === 3,
219 | value: v !== 3 && Yaku.resolve().then(function () {
220 | list.push(v);
221 | return ++v;
222 | })
223 | };
224 | } })(0).then(function (v) {
225 | list.push(v);
226 | return list;
227 | });
228 | }),
229 |
230 | it("promisify promise with this", "OK0", function () {
231 | var obj = {
232 | val: "OK",
233 | foo: function (val, cb) {
234 | return setTimeout(function (val) {
235 | return cb(null, val);
236 | }, 0, this.val + val);
237 | }
238 | };
239 | return utils.promisify(obj.foo, obj)(0);
240 | }),
241 |
242 | it("promisify promise", 1, function () {
243 | var fn;
244 | fn = utils.promisify(function (val, cb) {
245 | return setTimeout(function () {
246 | return cb(null, val + 1);
247 | });
248 | });
249 | return fn(0);
250 | }),
251 |
252 | it("promisify promise 2", 3, function () {
253 | var fn;
254 | fn = utils.promisify(function (a, b, cb) {
255 | return setTimeout(function () {
256 | return cb(null, a + b);
257 | });
258 | });
259 | return fn(1, 2);
260 | }),
261 |
262 | it("promisify promise err", "err", function () {
263 | var fn;
264 | fn = utils.promisify(function (a, cb) {
265 | return setTimeout(function () {
266 | return cb(a);
267 | });
268 | });
269 | return fn("err").catch(function (v) { return v });
270 | }),
271 |
272 | it("promisify callback", 1, function () {
273 | var fn;
274 | fn = utils.promisify(function (val, cb) {
275 | return setTimeout(function () {
276 | return cb(null, val + 1);
277 | });
278 | });
279 | return new Yaku(function (r) {
280 | return fn(0, function (err, val) {
281 | return r(val);
282 | });
283 | });
284 | }),
285 |
286 | it("Observable", "out: 9", function () {
287 | var one, three, tmr, two, x;
288 | one = new utils.Observable();
289 | x = 1;
290 |
291 | tmr = setInterval(function () {
292 | return one.emit(x++);
293 | }, 0);
294 |
295 | two = one.subscribe(function (v) {
296 | return v * v;
297 | });
298 |
299 | three = two.subscribe(function (v) {
300 | return "out: " + v;
301 | });
302 |
303 | return new Yaku(function (r) {
304 | var count;
305 | count = 0;
306 | return three.subscribe(function (v) {
307 | if (count++ === 2) {
308 | clearInterval(tmr);
309 | return r(v);
310 | }
311 | });
312 | });
313 | }),
314 |
315 | it("Observable error", "error", function () {
316 | var one, three, tmr, two, x;
317 | one = new utils.Observable();
318 | x = 1;
319 | tmr = setInterval(function () {
320 | one.emit(x++);
321 | if (x === 2) {
322 | return one.emit(Yaku.reject("error"));
323 | }
324 | }, 0);
325 |
326 | two = one.subscribe(function (v) {
327 | return v * v;
328 | });
329 |
330 | three = two.subscribe(function (v) {
331 | return "out: " + v;
332 | });
333 |
334 | return new Yaku(function (r) {
335 | return three.subscribe((function () {}), function (err) {
336 | clearInterval(tmr);
337 | return r(err);
338 | });
339 | });
340 | }),
341 |
342 | it("Observable subscribers", "ok", function () {
343 | var one, tmr;
344 | tmr = null;
345 | one = new utils.Observable(function (emit) {
346 | return tmr = setInterval(function () {
347 | return emit("err");
348 | }, 0);
349 | });
350 | return new Yaku(function (r) {
351 | setTimeout(function () {
352 | clearInterval(tmr);
353 | return r("ok");
354 | }, 10);
355 | one.subscribe(function (v) {
356 | return r(v);
357 | });
358 | return one.subscribers = [];
359 | });
360 | }),
361 |
362 | it("Observable unsubscribe null publisher", null, function () {
363 | var o = new utils.Observable();
364 | o.unsubscribe();
365 | return o.publisher;
366 | }),
367 |
368 | it("Observable unsubscribe", "ok", function () {
369 | return new Yaku(function (r) {
370 | var one = new utils.Observable(function (emit) {
371 | setTimeout(emit, 1);
372 | });
373 |
374 | var two = one.subscribe(function () {
375 | r("err");
376 | });
377 |
378 | setTimeout(function () {
379 | return r("ok");
380 | }, 10);
381 |
382 | two.unsubscribe();
383 | });
384 | }),
385 |
386 | it("Observable merge", ["one", "two"], function () {
387 | return new Yaku(function (r) {
388 | var flag = false;
389 |
390 | var one = new utils.Observable(function (emit) { setTimeout(emit, 0, "one"); });
391 | var two = new utils.Observable(function (emit) {
392 | setTimeout(function () {
393 | flag = true;
394 | emit("two");
395 | }, 0);
396 | });
397 |
398 | var three = utils.Observable.merge([one, two]);
399 | var out = [];
400 | three.subscribe(function (v) {
401 | out.push(v);
402 |
403 | if (flag) r(out);
404 | });
405 | });
406 | }),
407 |
408 | it("Observable merge error", 0, function () {
409 | var src = new utils.Observable(function (emit) {
410 | setTimeout(emit, 10, 0);
411 | });
412 |
413 | var a = src.subscribe(function (v) { return Yaku.reject(v); });
414 | var b = src.subscribe(function (v) { return Yaku.reject(v + 1); });
415 |
416 | var out = utils.Observable.merge([a, b]);
417 |
418 | return new Yaku(function (r, rr) {
419 | out.subscribe(rr, r);
420 | });
421 | }),
422 |
423 | it("retry once", "ok", function () {
424 | var fn;
425 | fn = function (val) {
426 | return val;
427 | };
428 | return utils.retry(3, fn)("ok");
429 | }),
430 |
431 | it("retry 2 times", "ok", function () {
432 | var count, fn;
433 | count = 0;
434 | fn = function (v) {
435 | if (count < 2) {
436 | throw "err" + count++;
437 | } else {
438 | return v;
439 | }
440 | };
441 | return utils.retry(5, fn)("ok");
442 | }),
443 |
444 | it("retry 3 times", ["err0", "err1", "err2"], function () {
445 | var count, fn;
446 | count = 0;
447 | fn = function () {
448 | throw "err" + count++;
449 | };
450 | return utils.retry(3, fn)()["catch"](function (errs) {
451 | return errs;
452 | });
453 | })
454 |
455 | ]; });
--------------------------------------------------------------------------------
/src/yaku.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | "use strict";
3 |
4 | var $nil
5 | , root = typeof global === "object" ? global : window
6 | , isLongStackTrace = false
7 |
8 | , $rejected = 0
9 | , $resolved = 1
10 | , $pending = 2
11 |
12 | , $promiseTrace = "_pt"
13 | , $settlerTrace = "_st"
14 |
15 | , $fromPrevious = "From previous event:"
16 | , $unhandledRejection = "unhandledRejection";
17 |
18 | /**
19 | * This class follows the [Promises/A+](https://promisesaplus.com) and
20 | * [ES6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) spec
21 | * with some extra helpers.
22 | * @param {Function} executor Function object with three arguments resolve, reject and
23 | * the promise itself.
24 | * The first argument fulfills the promise, the second argument rejects it.
25 | * We can call these functions, once our operation is completed.
26 | * The `this` context of the executor is the promise itself, it can be used to add custom handlers,
27 | * such as `abort` or `progress` helpers.
28 | * @example
29 | * Here's an abort example.
30 | * ```js
31 | * var Promise = require('yaku');
32 | * var p = new Promise((resolve, reject) => {
33 | * var tmr = setTimeout(resolve, 3000);
34 | * this.abort = (reason) => {
35 | * clearTimeout(tmr);
36 | * reject(reason);
37 | * };
38 | * });
39 | *
40 | * p.abort(new Error('abort'));
41 | * ```
42 | * @example
43 | * Here's a progress example.
44 | * ```js
45 | * var Promise = require('yaku');
46 | * var p = new Promise((resolve, reject) => {
47 | * var self = this;
48 | * var count = 0;
49 | * var all = 100;
50 | * var tmr = setInterval(() => {
51 | * try {
52 | * self.progress && self.progress(count, all);
53 | * } catch (err) {
54 | * reject(err);
55 | * }
56 | *
57 | * if (count < all)
58 | * count++;
59 | * else {
60 | * resolve();
61 | * clearInterval(tmr);
62 | * }
63 | * }, 1000);
64 | * });
65 | *
66 | * p.progress = (curr, all) => {
67 | * console.log(curr, '/', all);
68 | * };
69 | * ```
70 | */
71 | var Yaku = module.exports = function Promise (executor) {
72 | var self = this,
73 | err;
74 |
75 | if (isLongStackTrace) self[$promiseTrace] = genTraceInfo();
76 |
77 | if (executor !== $noop) {
78 | err = genTryCatcher(executor, self)(
79 | genSettler(self, $resolved),
80 | genSettler(self, $rejected)
81 | );
82 |
83 | if (err === $tryErr)
84 | settlePromise(self, $rejected, err.e);
85 | }
86 | };
87 |
88 | extendPrototype(Yaku, {
89 | /**
90 | * Appends fulfillment and rejection handlers to the promise,
91 | * and returns a new promise resolving to the return value of the called handler.
92 | * @param {Function} onFulfilled Optional. Called when the Promise is resolved.
93 | * @param {Function} onRejected Optional. Called when the Promise is rejected.
94 | * @return {Yaku} It will return a new Yaku which will resolve or reject after
95 | * @example
96 | * the current Promise.
97 | * ```js
98 | * var Promise = require('yaku');
99 | * var p = Promise.resolve(10);
100 | *
101 | * p.then((v) => {
102 | * console.log(v);
103 | * });
104 | * ```
105 | */
106 | then: function then (onFulfilled, onRejected) {
107 | return addHandler(this, newEmptyYaku(), onFulfilled, onRejected);
108 | },
109 |
110 | /**
111 | * The `catch()` method returns a Promise and deals with rejected cases only.
112 | * It behaves the same as calling `Promise.prototype.then(undefined, onRejected)`.
113 | * @param {Function} onRejected A Function called when the Promise is rejected.
114 | * This function has one argument, the rejection reason.
115 | * @return {Yaku} A Promise that deals with rejected cases only.
116 | * @example
117 | * ```js
118 | * var Promise = require('yaku');
119 | * var p = Promise.reject(new Error("ERR"));
120 | *
121 | * p['catch']((v) => {
122 | * console.log(v);
123 | * });
124 | * ```
125 | */
126 | "catch": function (onRejected) {
127 | return this.then($nil, onRejected);
128 | },
129 |
130 | // Default state
131 | _state: $pending,
132 |
133 | // The number of current promises that attach to this Yaku instance.
134 | _pCount: 0,
135 |
136 | // The parent Yaku.
137 | _pre: null,
138 |
139 | // A unique type flag, it helps different versions of Yaku know each other.
140 | _Yaku: 1
141 | });
142 |
143 | /**
144 | * The `Promise.resolve(value)` method returns a Promise object that is resolved with the given value.
145 | * If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable,
146 | * adopting its eventual state; otherwise the returned promise will be fulfilled with the value.
147 | * @param {Any} value Argument to be resolved by this Promise.
148 | * Can also be a Promise or a thenable to resolve.
149 | * @return {Yaku}
150 | * @example
151 | * ```js
152 | * var Promise = require('yaku');
153 | * var p = Promise.resolve(10);
154 | * ```
155 | */
156 | Yaku.resolve = function resolve (val) {
157 | return isYaku(val) ? val : settleWithX(newEmptyYaku(), val);
158 | };
159 |
160 | /**
161 | * The `Promise.reject(reason)` method returns a Promise object that is rejected with the given reason.
162 | * @param {Any} reason Reason why this Promise rejected.
163 | * @return {Yaku}
164 | * @example
165 | * ```js
166 | * var Promise = require('yaku');
167 | * var p = Promise.reject(new Error("ERR"));
168 | * ```
169 | */
170 | Yaku.reject = function reject (reason) {
171 | return settlePromise(newEmptyYaku(), $rejected, reason);
172 | };
173 |
174 | /**
175 | * The `Promise.race(iterable)` method returns a promise that resolves or rejects
176 | * as soon as one of the promises in the iterable resolves or rejects,
177 | * with the value or reason from that promise.
178 | * @param {iterable} iterable An iterable object, such as an Array.
179 | * @return {Yaku} The race function returns a Promise that is settled
180 | * the same way as the first passed promise to settle.
181 | * It resolves or rejects, whichever happens first.
182 | * @example
183 | * ```js
184 | * var Promise = require('yaku');
185 | * Promise.race([
186 | * 123,
187 | * Promise.resolve(0)
188 | * ])
189 | * .then((value) => {
190 | * console.log(value); // => 123
191 | * });
192 | * ```
193 | */
194 | Yaku.race = function race (iterable) {
195 | var iter, len, i = 0;
196 |
197 | var p = newEmptyYaku(), item;
198 |
199 | if (iterable instanceof Array) {
200 | len = iterable.length;
201 | while (i < len) {
202 | settleWithX(p, iterable[i++]);
203 | if (p._state !== $pending) break;
204 | }
205 | } else {
206 | iter = genIterator(iterable);
207 | while (!(item = iter.next()).done) {
208 | settleWithX(p, item.value);
209 | if (p._state !== $pending) break;
210 | }
211 | }
212 |
213 | return p;
214 | };
215 |
216 | /**
217 | * The `Promise.all(iterable)` method returns a promise that resolves when
218 | * all of the promises in the iterable argument have resolved.
219 | *
220 | * The result is passed as an array of values from all the promises.
221 | * If something passed in the iterable array is not a promise,
222 | * it's converted to one by Promise.resolve. If any of the passed in promises rejects,
223 | * the all Promise immediately rejects with the value of the promise that rejected,
224 | * discarding all the other promises whether or not they have resolved.
225 | * @param {iterable} iterable An iterable object, such as an Array.
226 | * @return {Yaku}
227 | * @example
228 | * ```js
229 | * var Promise = require('yaku');
230 | * Promise.all([
231 | * 123,
232 | * Promise.resolve(0)
233 | * ])
234 | * .then((values) => {
235 | * console.log(values); // => [123, 0]
236 | * });
237 | * ```
238 | * @example
239 | * Use with iterable.
240 | * ```js
241 | * var Promise = require('yaku');
242 | * Promise.all((function * () {
243 | * yield 10;
244 | * yield new Promise(function (r) { setTimeout(r, 1000, "OK") });
245 | * })())
246 | * .then((values) => {
247 | * console.log(values); // => [123, 0]
248 | * });
249 | * ```
250 | */
251 | Yaku.all = function all (iterable) {
252 | var p1 = newEmptyYaku()
253 | , res = []
254 | , item
255 | , countDown = 0
256 | , iter
257 | , len;
258 |
259 | function onRejected (reason) {
260 | settlePromise(p1, $rejected, reason);
261 | }
262 |
263 | if (iterable instanceof Array) {
264 | len = iterable.length;
265 | while (countDown < len) {
266 | runAll(countDown, iterable[countDown++], p1, res, onRejected);
267 | }
268 | } else {
269 | iter = genIterator(iterable);
270 | while (!(item = iter.next()).done) {
271 | runAll(countDown++, item.value, p1, res, onRejected);
272 | }
273 | }
274 |
275 | onRejected._c = countDown;
276 |
277 | if (!countDown) settlePromise(p1, $resolved, []);
278 |
279 | return p1;
280 | };
281 |
282 | function runAll (i, el, p1, res, onRejected) {
283 | Yaku.resolve(el).then(function (value) {
284 | res[i] = value;
285 | if (!--onRejected._c) settlePromise(p1, $resolved, res);
286 | }, onRejected);
287 | }
288 |
289 | /**
290 | * The ES6 Symbol object that Yaku should use, by default it will use the
291 | * global one.
292 | * @type {Object}
293 | * @example
294 | * ```js
295 | * var core = require("core-js/library");
296 | * var Promise = require("yaku");
297 | * Promise.Symbol = core.Symbol;
298 | * ```
299 | */
300 | Yaku.Symbol = root.Symbol || {};
301 |
302 | /**
303 | * Catch all possibly unhandled rejections. If you want to use specific
304 | * format to display the error stack, overwrite it.
305 | * If it is set, auto `console.error` unhandled rejection will be disabled.
306 | * @param {Any} reason The rejection reason.
307 | * @param {Yaku} p The promise that was rejected.
308 | * @example
309 | * ```js
310 | * var Promise = require('yaku');
311 | * Promise.onUnhandledRejection = (reason) => {
312 | * console.error(reason);
313 | * };
314 | *
315 | * // The console will log an unhandled rejection error message.
316 | * Promise.reject('my reason');
317 | *
318 | * // The below won't log the unhandled rejection error message.
319 | * Promise.reject('v').catch(() => {});
320 | * ```
321 | */
322 | Yaku.onUnhandledRejection = function (reason, p) {
323 | var con = root.console;
324 | if (con) {
325 | var info = genStackInfo(reason, p);
326 | con.error($unhandledRejection, info[0], info[1] || "");
327 | }
328 | };
329 |
330 | /**
331 | * It is used to enable the long stack trace.
332 | * Once it is enabled, it can't be reverted.
333 | * While it is very helpful in development and testing environments,
334 | * it is not recommended to use it in production. It will slow down your
335 | * application and waste your memory.
336 | * @example
337 | * ```js
338 | * var Promise = require('yaku');
339 | * Promise.enableLongStackTrace();
340 | * ```
341 | */
342 | Yaku.enableLongStackTrace = function () {
343 | isLongStackTrace = true;
344 | };
345 |
346 | /**
347 | * Only Node has `process.nextTick` function. For browser there are
348 | * so many ways to polyfill it. Yaku won't do it for you, instead you
349 | * can choose what you prefer. For example, this project
350 | * [setImmediate](https://github.com/YuzuJS/setImmediate).
351 | * By default, Yaku will use `process.nextTick` on Node, `setTimeout` on browser.
352 | * @type {Function}
353 | * @example
354 | * ```js
355 | * var Promise = require('yaku');
356 | * Promise.nextTick = fn => window.setImmediate(fn);
357 | * ```
358 | * @example
359 | * You can even use sync resolution if you really know what you are doing.
360 | * ```js
361 | * var Promise = require('yaku');
362 | * Promise.nextTick = fn => fn();
363 | * ```
364 | */
365 | Yaku.nextTick = root.process ?
366 | root.process.nextTick :
367 | function (fn) { setTimeout(fn); };
368 |
369 | // ********************** Private **********************
370 |
371 | /**
372 | * All static variable name will begin with `$`. Such as `$rejected`.
373 | * @private
374 | */
375 |
376 | // ******************************* Utils ********************************
377 |
378 | var $tryCatchFn
379 | , $tryCatchThis
380 | , $tryErr = { e: null }
381 | , $noop = {};
382 |
383 | function extendPrototype (src, target) {
384 | for (var k in target) {
385 | src.prototype[k] = target[k];
386 | }
387 | return src;
388 | }
389 |
390 | function isObject (obj) {
391 | return typeof obj === "object";
392 | }
393 |
394 | function isFunction (obj) {
395 | return typeof obj === "function";
396 | }
397 |
398 | /**
399 | * Wrap a function into a try-catch.
400 | * @private
401 | * @return {Any | $tryErr}
402 | */
403 | function tryCatcher () {
404 | try {
405 | return $tryCatchFn.apply($tryCatchThis, arguments);
406 | } catch (e) {
407 | $tryErr.e = e;
408 | return $tryErr;
409 | }
410 | }
411 |
412 | /**
413 | * Generate a try-catch wrapped function.
414 | * @private
415 | * @param {Function} fn
416 | * @return {Function}
417 | */
418 | function genTryCatcher (fn, self) {
419 | $tryCatchFn = fn;
420 | $tryCatchThis = self;
421 | return tryCatcher;
422 | }
423 |
424 | /**
425 | * Generate a scheduler.
426 | * @private
427 | * @param {Integer} initQueueSize
428 | * @param {Function} fn `(Yaku, Value) ->` The schedule handler.
429 | * @return {Function} `(Yaku, Value) ->` The scheduler.
430 | */
431 | function genScheduler (initQueueSize, fn) {
432 | /**
433 | * All async promise will be scheduled in
434 | * here, so that they can be execute on the next tick.
435 | * @private
436 | */
437 | var fnQueue = Array(initQueueSize)
438 | , fnQueueLen = 0;
439 |
440 | /**
441 | * Run all queued functions.
442 | * @private
443 | */
444 | function flush () {
445 | var i = 0;
446 | while (i < fnQueueLen) {
447 | fn(fnQueue[i], fnQueue[i + 1]);
448 | fnQueue[i++] = $nil;
449 | fnQueue[i++] = $nil;
450 | }
451 |
452 | fnQueueLen = 0;
453 | if (fnQueue.length > initQueueSize) fnQueue.length = initQueueSize;
454 | }
455 |
456 | return function (v, arg) {
457 | fnQueue[fnQueueLen++] = v;
458 | fnQueue[fnQueueLen++] = arg;
459 |
460 | if (fnQueueLen === 2) Yaku.nextTick(flush);
461 | };
462 | }
463 |
464 | /**
465 | * Generate a iterator
466 | * @param {Any} obj
467 | * @return {Function}
468 | */
469 | function genIterator (obj) {
470 | if (obj) {
471 | var gen = obj[Yaku.Symbol.iterator];
472 | if (isFunction(gen)) {
473 | return gen.call(obj);
474 | }
475 |
476 | if (isFunction(obj.next)) {
477 | return obj;
478 | }
479 | }
480 | throw genTypeError("invalid_argument");
481 | }
482 |
483 | /**
484 | * Generate type error object.
485 | * @private
486 | * @param {String} msg
487 | * @return {TypeError}
488 | */
489 | function genTypeError (msg) {
490 | return new TypeError(msg);
491 | }
492 |
493 | function genTraceInfo (noTitle) {
494 | return ((new Error()).stack || "").replace(
495 | "Error",
496 | noTitle ? "" : $fromPrevious
497 | );
498 | }
499 |
500 |
501 | // *************************** Promise Helpers ****************************
502 |
503 | /**
504 | * Resolve the value returned by onFulfilled or onRejected.
505 | * @private
506 | * @param {Yaku} p1
507 | * @param {Yaku} p2
508 | */
509 | var scheduleHandler = genScheduler(999, function (p1, p2) {
510 | var x, handler;
511 |
512 | // 2.2.2
513 | // 2.2.3
514 | handler = p1._state ? p2._onFulfilled : p2._onRejected;
515 |
516 | // 2.2.7.3
517 | // 2.2.7.4
518 | if (handler === $nil) {
519 | settlePromise(p2, p1._state, p1._value);
520 | return;
521 | }
522 |
523 | // 2.2.7.1
524 | x = genTryCatcher(callHanler)(handler, p1._value);
525 | if (x === $tryErr) {
526 | // 2.2.7.2
527 | settlePromise(p2, $rejected, x.e);
528 | return;
529 | }
530 |
531 | settleWithX(p2, x);
532 | });
533 |
534 | var scheduleUnhandledRejection = genScheduler(9, function (p) {
535 | if (!hashOnRejected(p)) {
536 | var process = root.process
537 | , onunhandledrejection = root.onunhandledrejection
538 | , reason = p._value;
539 |
540 | if (process && process.listeners($unhandledRejection).length)
541 | process.emit($unhandledRejection, reason, p);
542 | else if (onunhandledrejection)
543 | onunhandledrejection({ promise: p, reason: reason });
544 | else
545 | Yaku.onUnhandledRejection(reason, p);
546 | }
547 | });
548 |
549 | function isYaku (val) { return val && val._Yaku; }
550 |
551 | /**
552 | * Create an empty promise.
553 | * @private
554 | * @return {Yaku}
555 | */
556 | function newEmptyYaku () { return new Yaku($noop); }
557 |
558 | /**
559 | * It will produce a settlePromise function to user.
560 | * Such as the resolve and reject in this `new Yaku (resolve, reject) ->`.
561 | * @private
562 | * @param {Yaku} self
563 | * @param {Integer} state The value is one of `$pending`, `$resolved` or `$rejected`.
564 | * @return {Function} `(value) -> undefined` A resolve or reject function.
565 | */
566 | function genSettler (self, state) { return function (value) {
567 | if (isLongStackTrace)
568 | self[$settlerTrace] = genTraceInfo(true);
569 |
570 | if (state === $resolved)
571 | settleWithX(self, value);
572 | else
573 | settlePromise(self, state, value);
574 | }; }
575 |
576 | /**
577 | * Link the promise1 to the promise2.
578 | * @private
579 | * @param {Yaku} p1
580 | * @param {Yaku} p2
581 | * @param {Function} onFulfilled
582 | * @param {Function} onRejected
583 | */
584 | function addHandler (p1, p2, onFulfilled, onRejected) {
585 | // 2.2.1
586 | if (isFunction(onFulfilled))
587 | p2._onFulfilled = onFulfilled;
588 | if (isFunction(onRejected))
589 | p2._onRejected = onRejected;
590 |
591 | if (isLongStackTrace) p2._pre = p1;
592 | p1[p1._pCount++] = p2;
593 |
594 | // 2.2.6
595 | if (p1._state !== $pending)
596 | scheduleHandler(p1, p2);
597 |
598 | // 2.2.7
599 | return p2;
600 | }
601 |
602 | // iterate tree
603 | function hashOnRejected (node) {
604 | // A node shouldn't be checked twice.
605 | if (node._umark)
606 | return true;
607 | else
608 | node._umark = true;
609 |
610 | var i = 0
611 | , len = node._pCount
612 | , child;
613 |
614 | while (i < len) {
615 | child = node[i++];
616 | if (child._onRejected || hashOnRejected(child)) return true;
617 | }
618 | }
619 |
620 | function genStackInfo (reason, p) {
621 | var stackInfo = []
622 | , stackStr
623 | , i;
624 |
625 | function trim (str) { return str.replace(/^\s+|\s+$/g, ""); }
626 |
627 | function push (trace) {
628 | return stackInfo.push(trim(trace));
629 | }
630 |
631 | if (isLongStackTrace && p[$promiseTrace]) {
632 | if (p[$settlerTrace])
633 | push(p[$settlerTrace]);
634 |
635 | // Hope you guys could understand how the back trace works.
636 | // We only have to iterate through the tree from the bottom to root.
637 | (function iter (node) {
638 | if (node) {
639 | iter(node._next);
640 | push(node[$promiseTrace]);
641 | iter(node._pre);
642 | }
643 | })(p);
644 | }
645 |
646 | stackStr = "\n" + stackInfo.join("\n");
647 |
648 | function clean (stack, cleanPrev) {
649 | if (cleanPrev && (i = stack.indexOf("\n" + $fromPrevious)) > 0)
650 | stack = stack.slice(0, i);
651 |
652 | return stack.replace(/^.+\/node_modules\/yaku\/.+\n?/mg, "");
653 | }
654 |
655 | return [(
656 | reason ?
657 | reason.stack ?
658 | clean(trim(reason.stack), true)
659 | :
660 | reason
661 | :
662 | reason
663 | ), clean(stackStr)];
664 | }
665 |
666 | function callHanler (handler, value) {
667 | // 2.2.5
668 | return handler(value);
669 | }
670 |
671 | /**
672 | * Resolve or reject a promise.
673 | * @private
674 | * @param {Yaku} p
675 | * @param {Integer} state
676 | * @param {Any} value
677 | */
678 | function settlePromise (p, state, value) {
679 | var i = 0
680 | , len = p._pCount
681 | , p2
682 | , stack;
683 |
684 | // 2.1.2
685 | // 2.1.3
686 | if (p._state === $pending) {
687 | // 2.1.1.1
688 | p._state = state;
689 | p._value = value;
690 |
691 | if (state === $rejected) {
692 | if (isLongStackTrace && value && value.stack) {
693 | stack = genStackInfo(value, p);
694 | value.stack = stack[0] + stack[1];
695 | }
696 |
697 | scheduleUnhandledRejection(p);
698 | }
699 |
700 | // 2.2.4
701 | while (i < len) {
702 | p2 = p[i++];
703 |
704 | if (p2._state !== $pending) continue;
705 |
706 | scheduleHandler(p, p2);
707 | }
708 | }
709 |
710 | return p;
711 | }
712 |
713 | /**
714 | * Resolve or reject promise with value x. The x can also be a thenable.
715 | * @private
716 | * @param {Yaku} p
717 | * @param {Any | Thenable} x A normal value or a thenable.
718 | */
719 | function settleWithX (p, x) {
720 | // 2.3.1
721 | if (x === p && x) {
722 | settlePromise(p, $rejected, genTypeError("promise_circular_chain"));
723 | return p;
724 | }
725 |
726 | // 2.3.2
727 | // 2.3.3
728 | if (x != null && (isFunction(x) || isObject(x))) {
729 | // 2.3.2.1
730 | var xthen = genTryCatcher(getThen)(x);
731 |
732 | if (xthen === $tryErr) {
733 | // 2.3.3.2
734 | settlePromise(p, $rejected, xthen.e);
735 | return p;
736 | }
737 |
738 | if (isFunction(xthen)) {
739 | if (isLongStackTrace && isYaku(x))
740 | p._next = x;
741 |
742 | settleXthen(p, x, xthen);
743 | }
744 | else
745 | // 2.3.3.4
746 | settlePromise(p, $resolved, x);
747 | } else
748 | // 2.3.4
749 | settlePromise(p, $resolved, x);
750 |
751 | return p;
752 | }
753 |
754 | /**
755 | * Try to get a promise's then method.
756 | * @private
757 | * @param {Thenable} x
758 | * @return {Function}
759 | */
760 | function getThen (x) { return x.then; }
761 |
762 | /**
763 | * Resolve then with its promise.
764 | * @private
765 | * @param {Yaku} p
766 | * @param {Thenable} x
767 | * @param {Function} xthen
768 | */
769 | function settleXthen (p, x, xthen) {
770 | // 2.3.3.3
771 | var err = genTryCatcher(xthen, x)(function (y) {
772 | // 2.3.3.3.3
773 | if (x) {
774 | x = null;
775 |
776 | // 2.3.3.3.1
777 | settleWithX(p, y);
778 | }
779 | }, function (r) {
780 | // 2.3.3.3.3
781 | if (x) {
782 | x = null;
783 |
784 | // 2.3.3.3.2
785 | settlePromise(p, $rejected, r);
786 | }
787 | });
788 |
789 | // 2.3.3.3.4.1
790 | if (err === $tryErr && x) {
791 | // 2.3.3.3.4.2
792 | settlePromise(p, $rejected, err.e);
793 | x = null;
794 | }
795 | }
796 |
797 | })();
798 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | # Overview
7 |
8 | Yaku is full compatible with ES6's native [Promise][native], but much faster, and more error friendly.
9 | If you want to learn how Promise works, read the minimum implementation [docs/minPromiseA+.coffee][]. Without comments, it is only 80 lines of code (gzipped size is 0.5KB).
10 | It only implements the `constructor` and `then`. It passed all the tests of [promises-aplus-tests][].
11 |
12 | I am not an optimization freak, I try to keep the source code readable and maintainable.
13 | Premature optimization is the root of all evil. I write this lib to research one of my data structure
14 | ideas: [docs/lazyTree.md][].
15 |
16 | [](http://badge.fury.io/js/yaku) [](https://travis-ci.org/ysmood/yaku) [](https://david-dm.org/ysmood/yaku)
17 |
18 |
19 |
20 | # Features
21 |
22 | - The minified file is only 3.5KB (1.5KB gzipped) ([Bluebird][] / 73KB, [ES6-promise][] / 18KB)
23 | - [Better "possibly unhandled rejection" and "long stack trace"][docs/debugHelperComparison.md] than [Bluebird][]
24 | - Much better performance than the native Promise
25 | - 100% compliant with Promises/A+ specs and ES6
26 | - Designed to work on IE5+ and other major browsers
27 | - Well commented source code with every Promises/A+ spec
28 |
29 |
30 |
31 | # Quick Start
32 |
33 | ## Node.js
34 |
35 | ```shell
36 | npm install yaku
37 | ```
38 |
39 | Then:
40 | ```js
41 | var Promise = require('yaku');
42 | ```
43 |
44 |
45 |
46 | ## Browser
47 |
48 | Use something like [Browserify][] or [Webpack][], or download the `yaku.js` file from [release page][].
49 | Raw usage without:
50 |
51 | ```html
52 |
53 |
57 | ```
58 |
59 |
60 |
61 | # Change Log
62 |
63 | [docs/changelog.md](docs/changelog.md)
64 |
65 |
66 |
67 | # Compare to Other Promise Libs
68 |
69 | These comparisons only reflect some limited truth, no one is better than all others on all aspects.
70 | For more details see the [benchmark/readme.md](benchmark/readme.md). There are tons of Promises/A+ implementations, you can see them [here](https://promisesaplus.com/implementations). Only some of the famous ones were tested.
71 |
72 | | Name | 1ms async task / mem | sync task / mem | Helpers | file size |
73 | | -------------------- | -------------------- | --------------- | ------- | --------- |
74 | | Yaku | 257ms / 110MB | 126ms / 80MB | +++ | 3.5KB |
75 | | [Bluebird][] v2.9 | 249ms / 102MB | 155ms / 80MB | +++++++ | 73KB |
76 | | [ES6-promise][] v2.3 | 427ms / 120MB | 92ms / 78MB | + | 18KB |
77 | | [native][] iojs v1.8 | 789ms / 189MB | 605ms / 147MB | + | 0KB |
78 | | [q][] v1.3 | 2648ms / 646MB | 2373ms / 580MB | +++ | 24K |
79 |
80 | - **Helpers**: extra methods that help with your promise programming, such as
81 | async flow control helpers, debug helpers. For more details: [docs/debugHelperComparison.md][].
82 | - **1ms async task**: `npm run no -- benchmark`, the smaller the better.
83 | - **sync task**: `npm run no -- benchmark --sync`, the smaller the better.
84 |
85 |
86 |
87 | # FAQ
88 |
89 | - `catch` on old brwoser (IE7, IE8 etc)?
90 |
91 | > In ECMA-262 spec, `catch` cannot be used as method name. You have to alias the method name or use something like `Promise.resolve()['catch'](function() {})` or `Promise.resolve().then(null, function() {})`.
92 |
93 | - Will Yaku implement `done`, `finally`, `promisify`, etc?
94 |
95 | > No. All non-ES6 APIs are only implemented for debugging and testing, which means when you remove Yaku, everything
96 | > should work well with ES6 native promise. If you need fancy and magic, go for [Bluebird][].
97 |
98 | - When using with Babel and Regenerator, the unhandled rejection doesn't work.
99 |
100 | > Because Regenerator use global Promise directly and don't have an api to set the Promise lib.
101 | > You have to import Yaku globally to make it use Yaku: `require("yaku/lib/global");`.
102 |
103 | - Better long stack trace support?
104 |
105 | > Latest Node.js and browsers are already support it. If you enabled it, Yaku will take advantage of it
106 | > without much overhead. Such as this library [longjohn][] for Node.js, or this article for [Chrome][crhome-lst].
107 |
108 | - The name Yaku is weird?
109 |
110 | > The name `yaku` comes from the word `約束(yakusoku)` which means promise.
111 |
112 |
113 | # Unhandled Rejection
114 |
115 | Yaku will report any unhandled rejection via `console.error` by default, in case you forget to write `catch`.
116 | You can catch with them manually:
117 |
118 | - Browser: `window.onunhandledrejection = ({ promise, reason }) => { /* Your Code */ };`
119 | - Node: `process.on("unhandledRejection", (reason, promise) => { /* Your Code */ });`
120 |
121 | For more spec read [Unhandled Rejection Tracking Browser Events](https://github.com/domenic/unhandled-rejections-browser-spec).
122 |
123 |
124 | # API
125 |
126 | - ### **[Yaku(executor)](src/yaku.js?source#L71)**
127 |
128 | This class follows the [Promises/A+](https://promisesaplus.com) and
129 | [ES6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) spec
130 | with some extra helpers.
131 |
132 | - **param**: `executor` { _Function_ }
133 |
134 | Function object with three arguments resolve, reject and
135 | the promise itself.
136 | The first argument fulfills the promise, the second argument rejects it.
137 | We can call these functions, once our operation is completed.
138 | The `this` context of the executor is the promise itself, it can be used to add custom handlers,
139 | such as `abort` or `progress` helpers.
140 |
141 | - **example**:
142 |
143 | Here's an abort example.
144 | ```js
145 | var Promise = require('yaku');
146 | var p = new Promise((resolve, reject) => {
147 | var tmr = setTimeout(resolve, 3000);
148 | this.abort = (reason) => {
149 | clearTimeout(tmr);
150 | reject(reason);
151 | };
152 | });
153 |
154 | p.abort(new Error('abort'));
155 | ```
156 |
157 | - **example**:
158 |
159 | Here's a progress example.
160 | ```js
161 | var Promise = require('yaku');
162 | var p = new Promise((resolve, reject) => {
163 | var self = this;
164 | var count = 0;
165 | var all = 100;
166 | var tmr = setInterval(() => {
167 | try {
168 | self.progress && self.progress(count, all);
169 | } catch (err) {
170 | reject(err);
171 | }
172 |
173 | if (count < all)
174 | count++;
175 | else {
176 | resolve();
177 | clearInterval(tmr);
178 | }
179 | }, 1000);
180 | });
181 |
182 | p.progress = (curr, all) => {
183 | console.log(curr, '/', all);
184 | };
185 | ```
186 |
187 | - ### **[then(onFulfilled, onRejected)](src/yaku.js?source#L106)**
188 |
189 | Appends fulfillment and rejection handlers to the promise,
190 | and returns a new promise resolving to the return value of the called handler.
191 |
192 | - **param**: `onFulfilled` { _Function_ }
193 |
194 | Optional. Called when the Promise is resolved.
195 |
196 | - **param**: `onRejected` { _Function_ }
197 |
198 | Optional. Called when the Promise is rejected.
199 |
200 | - **return**: { _Yaku_ }
201 |
202 | It will return a new Yaku which will resolve or reject after
203 |
204 | - **example**:
205 |
206 | the current Promise.
207 | ```js
208 | var Promise = require('yaku');
209 | var p = Promise.resolve(10);
210 |
211 | p.then((v) => {
212 | console.log(v);
213 | });
214 | ```
215 |
216 | - ### **[catch(onRejected)](src/yaku.js?source#L126)**
217 |
218 | The `catch()` method returns a Promise and deals with rejected cases only.
219 | It behaves the same as calling `Promise.prototype.then(undefined, onRejected)`.
220 |
221 | - **param**: `onRejected` { _Function_ }
222 |
223 | A Function called when the Promise is rejected.
224 | This function has one argument, the rejection reason.
225 |
226 | - **return**: { _Yaku_ }
227 |
228 | A Promise that deals with rejected cases only.
229 |
230 | - **example**:
231 |
232 | ```js
233 | var Promise = require('yaku');
234 | var p = Promise.reject(new Error("ERR"));
235 |
236 | p['catch']((v) => {
237 | console.log(v);
238 | });
239 | ```
240 |
241 | - ### **[Yaku.resolve(value)](src/yaku.js?source#L156)**
242 |
243 | The `Promise.resolve(value)` method returns a Promise object that is resolved with the given value.
244 | If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable,
245 | adopting its eventual state; otherwise the returned promise will be fulfilled with the value.
246 |
247 | - **param**: `value` { _Any_ }
248 |
249 | Argument to be resolved by this Promise.
250 | Can also be a Promise or a thenable to resolve.
251 |
252 | - **return**: { _Yaku_ }
253 |
254 | - **example**:
255 |
256 | ```js
257 | var Promise = require('yaku');
258 | var p = Promise.resolve(10);
259 | ```
260 |
261 | - ### **[Yaku.reject(reason)](src/yaku.js?source#L170)**
262 |
263 | The `Promise.reject(reason)` method returns a Promise object that is rejected with the given reason.
264 |
265 | - **param**: `reason` { _Any_ }
266 |
267 | Reason why this Promise rejected.
268 |
269 | - **return**: { _Yaku_ }
270 |
271 | - **example**:
272 |
273 | ```js
274 | var Promise = require('yaku');
275 | var p = Promise.reject(new Error("ERR"));
276 | ```
277 |
278 | - ### **[Yaku.race(iterable)](src/yaku.js?source#L194)**
279 |
280 | The `Promise.race(iterable)` method returns a promise that resolves or rejects
281 | as soon as one of the promises in the iterable resolves or rejects,
282 | with the value or reason from that promise.
283 |
284 | - **param**: `iterable` { _iterable_ }
285 |
286 | An iterable object, such as an Array.
287 |
288 | - **return**: { _Yaku_ }
289 |
290 | The race function returns a Promise that is settled
291 | the same way as the first passed promise to settle.
292 | It resolves or rejects, whichever happens first.
293 |
294 | - **example**:
295 |
296 | ```js
297 | var Promise = require('yaku');
298 | Promise.race([
299 | 123,
300 | Promise.resolve(0)
301 | ])
302 | .then((value) => {
303 | console.log(value); // => 123
304 | });
305 | ```
306 |
307 | - ### **[Yaku.all(iterable)](src/yaku.js?source#L251)**
308 |
309 | The `Promise.all(iterable)` method returns a promise that resolves when
310 | all of the promises in the iterable argument have resolved.
311 |
312 | The result is passed as an array of values from all the promises.
313 | If something passed in the iterable array is not a promise,
314 | it's converted to one by Promise.resolve. If any of the passed in promises rejects,
315 | the all Promise immediately rejects with the value of the promise that rejected,
316 | discarding all the other promises whether or not they have resolved.
317 |
318 | - **param**: `iterable` { _iterable_ }
319 |
320 | An iterable object, such as an Array.
321 |
322 | - **return**: { _Yaku_ }
323 |
324 | - **example**:
325 |
326 | ```js
327 | var Promise = require('yaku');
328 | Promise.all([
329 | 123,
330 | Promise.resolve(0)
331 | ])
332 | .then((values) => {
333 | console.log(values); // => [123, 0]
334 | });
335 | ```
336 |
337 | - **example**:
338 |
339 | Use with iterable.
340 | ```js
341 | var Promise = require('yaku');
342 | Promise.all((function * () {
343 | yield 10;
344 | yield new Promise(function (r) { setTimeout(r, 1000, "OK") });
345 | })())
346 | .then((values) => {
347 | console.log(values); // => [123, 0]
348 | });
349 | ```
350 |
351 | - ### **[Yaku.Symbol](src/yaku.js?source#L300)**
352 |
353 | The ES6 Symbol object that Yaku should use, by default it will use the
354 | global one.
355 |
356 | - **type**: { _Object_ }
357 |
358 | - **example**:
359 |
360 | ```js
361 | var core = require("core-js/library");
362 | var Promise = require("yaku");
363 | Promise.Symbol = core.Symbol;
364 | ```
365 |
366 | - ### **[Yaku.onUnhandledRejection(reason, p)](src/yaku.js?source#L322)**
367 |
368 | Catch all possibly unhandled rejections. If you want to use specific
369 | format to display the error stack, overwrite it.
370 | If it is set, auto `console.error` unhandled rejection will be disabled.
371 |
372 | - **param**: `reason` { _Any_ }
373 |
374 | The rejection reason.
375 |
376 | - **param**: `p` { _Yaku_ }
377 |
378 | The promise that was rejected.
379 |
380 | - **example**:
381 |
382 | ```js
383 | var Promise = require('yaku');
384 | Promise.onUnhandledRejection = (reason) => {
385 | console.error(reason);
386 | };
387 |
388 | // The console will log an unhandled rejection error message.
389 | Promise.reject('my reason');
390 |
391 | // The below won't log the unhandled rejection error message.
392 | Promise.reject('v').catch(() => {});
393 | ```
394 |
395 | - ### **[Yaku.enableLongStackTrace](src/yaku.js?source#L342)**
396 |
397 | It is used to enable the long stack trace.
398 | Once it is enabled, it can't be reverted.
399 | While it is very helpful in development and testing environments,
400 | it is not recommended to use it in production. It will slow down your
401 | application and waste your memory.
402 |
403 | - **example**:
404 |
405 | ```js
406 | var Promise = require('yaku');
407 | Promise.enableLongStackTrace();
408 | ```
409 |
410 | - ### **[Yaku.nextTick](src/yaku.js?source#L365)**
411 |
412 | Only Node has `process.nextTick` function. For browser there are
413 | so many ways to polyfill it. Yaku won't do it for you, instead you
414 | can choose what you prefer. For example, this project
415 | [setImmediate](https://github.com/YuzuJS/setImmediate).
416 | By default, Yaku will use `process.nextTick` on Node, `setTimeout` on browser.
417 |
418 | - **type**: { _Function_ }
419 |
420 | - **example**:
421 |
422 | ```js
423 | var Promise = require('yaku');
424 | Promise.nextTick = fn => window.setImmediate(fn);
425 | ```
426 |
427 | - **example**:
428 |
429 | You can even use sync resolution if you really know what you are doing.
430 | ```js
431 | var Promise = require('yaku');
432 | Promise.nextTick = fn => fn();
433 | ```
434 |
435 | - ### **[genIterator(obj)](src/yaku.js?source#L469)**
436 |
437 | Generate a iterator
438 |
439 | - **param**: `obj` { _Any_ }
440 |
441 | - **return**: { _Function_ }
442 |
443 |
444 |
445 |
446 |
447 | # Utils
448 |
449 | It's a bundle of all the following functions. You can require them all with `var yutils = require("yaku/lib/utils")`,
450 | or require them separately like `require("yaku/lib/flow")`. If you want to use it in the browser, you have to use `browserify` or `webpack`. You can even use another Promise lib, such as:
451 |
452 | ```js
453 | require("yaku/lib/_").Promise = require("bluebird");
454 | var source = require("yaku/lib/source");
455 |
456 | // now "source" use bluebird instead of yaku.
457 | ```
458 |
459 | - ### **[any(iterable)](src/utils.js?source#L22)**
460 |
461 | Similar with the `Promise.race`, but only rejects when every entry rejects.
462 |
463 | - **param**: `iterable` { _iterable_ }
464 |
465 | An iterable object, such as an Array.
466 |
467 | - **return**: { _Yaku_ }
468 |
469 | - **example**:
470 |
471 | ```js
472 | var any = require('yaku/lib/any');
473 | any([
474 | 123,
475 | Promise.resolve(0),
476 | Promise.reject(new Error("ERR"))
477 | ])
478 | .then((value) => {
479 | console.log(value); // => 123
480 | });
481 | ```
482 |
483 | - ### **[async(limit, list, saveResults, progress)](src/utils.js?source#L69)**
484 |
485 | A function that helps run functions under a concurrent limitation.
486 | To run functions sequentially, use `yaku/lib/flow`.
487 |
488 | - **param**: `limit` { _Int_ }
489 |
490 | The max task to run at a time. It's optional.
491 | Default is `Infinity`.
492 |
493 | - **param**: `list` { _Iterable_ }
494 |
495 | Any [iterable](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) object. It should be a lazy iteralbe object,
496 | don't pass in a normal Array with promises.
497 |
498 | - **param**: `saveResults` { _Boolean_ }
499 |
500 | Whether to save each promise's result or
501 | not. Default is true.
502 |
503 | - **param**: `progress` { _Function_ }
504 |
505 | If a task ends, the resolved value will be
506 | passed to this function.
507 |
508 | - **return**: { _Promise_ }
509 |
510 | - **example**:
511 |
512 | ```js
513 | var kit = require('nokit');
514 | var async = require('yaku/lib/async');
515 |
516 | var urls = [
517 | 'http://a.com',
518 | 'http://b.com',
519 | 'http://c.com',
520 | 'http://d.com'
521 | ];
522 | var tasks = function * () {
523 | var i = 0;
524 | yield kit.request(url[i++]);
525 | yield kit.request(url[i++]);
526 | yield kit.request(url[i++]);
527 | yield kit.request(url[i++]);
528 | }();
529 |
530 | async(tasks).then(() => kit.log('all done!'));
531 |
532 | async(2, tasks).then(() => kit.log('max concurrent limit is 2'));
533 |
534 | async(3, { next: () => {
535 | var url = urls.pop();
536 | return {
537 | done: !url,
538 | value: url && kit.request(url)
539 | };
540 | } })
541 | .then(() => kit.log('all done!'));
542 | ```
543 |
544 | - ### **[callbackify(fn, self)](src/utils.js?source#L78)**
545 |
546 | If a function returns promise, convert it to
547 | node callback style function.
548 |
549 | - **param**: `fn` { _Function_ }
550 |
551 | - **param**: `self` { _Any_ }
552 |
553 | The `this` to bind to the fn.
554 |
555 | - **return**: { _Function_ }
556 |
557 | - ### **[Deferred](src/utils.js?source#L84)**
558 |
559 | **deprecate** Create a `jQuery.Deferred` like object.
560 | It will cause some buggy problems, please don't use it.
561 |
562 | - ### **[flow(list)](src/utils.js?source#L142)**
563 |
564 | Creates a function that is the composition of the provided functions.
565 | See `yaku/lib/async`, if you need concurrent support.
566 |
567 | - **param**: `list` { _Iterable_ }
568 |
569 | Any [iterable](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols) object. It should be a lazy iteralbe object,
570 | don't pass in a normal Array with promises.
571 |
572 | - **return**: { _Function_ }
573 |
574 | `(val) -> Promise` A function that will return a promise.
575 |
576 | - **example**:
577 |
578 | It helps to decouple sequential pipeline code logic.
579 | ```js
580 | var kit = require('nokit');
581 | var flow = require('yaku/lib/flow');
582 |
583 | function createUrl (name) {
584 | return "http://test.com/" + name;
585 | }
586 |
587 | function curl (url) {
588 | return kit.request(url).then((body) => {
589 | kit.log('get');
590 | return body;
591 | });
592 | }
593 |
594 | function save (str) {
595 | kit.outputFile('a.txt', str).then(() => {
596 | kit.log('saved');
597 | });
598 | }
599 |
600 | var download = flow(createUrl, curl, save);
601 | // same as "download = flow([createUrl, curl, save])"
602 |
603 | download('home');
604 | ```
605 |
606 | - **example**:
607 |
608 | Walk through first link of each page.
609 | ```js
610 | var kit = require('nokit');
611 | var flow = require('yaku/lib/flow');
612 |
613 | var list = [];
614 | function iter (url) {
615 | return {
616 | done: !url,
617 | value: url && kit.request(url).then((body) => {
618 | list.push(body);
619 | var m = body.match(/href="(.+?)"/);
620 | if (m) return m[0];
621 | });
622 | };
623 | }
624 |
625 | var walker = flow(iter);
626 | walker('test.com');
627 | ```
628 |
629 | - ### **[isPromise(obj)](src/utils.js?source#L150)**
630 |
631 | **deprecate** Check if an object is a promise-like object.
632 | Don't use it to coercive a value to Promise, instead use `Promise.resolve`.
633 |
634 | - **param**: `obj` { _Any_ }
635 |
636 | - **return**: { _Boolean_ }
637 |
638 | - ### **[never()](src/utils.js?source#L156)**
639 |
640 | Create a promise that never ends.
641 |
642 | - **return**: { _Promise_ }
643 |
644 | A promise that will end the current pipeline.
645 |
646 | - ### **[promisify(fn, self)](src/utils.js?source#L185)**
647 |
648 | Convert a node callback style function to a function that returns
649 | promise when the last callback is not supplied.
650 |
651 | - **param**: `fn` { _Function_ }
652 |
653 | - **param**: `self` { _Any_ }
654 |
655 | The `this` to bind to the fn.
656 |
657 | - **return**: { _Function_ }
658 |
659 | - **example**:
660 |
661 | ```js
662 | var promisify = require('yaku/lib/promisify');
663 | function foo (val, cb) {
664 | setTimeout(() => {
665 | cb(null, val + 1);
666 | });
667 | }
668 |
669 | var bar = promisify(foo);
670 |
671 | bar(0).then((val) => {
672 | console.log val // output => 1
673 | });
674 |
675 | // It also supports the callback style.
676 | bar(0, (err, val) => {
677 | console.log(val); // output => 1
678 | });
679 | ```
680 |
681 | - ### **[sleep(time, val)](src/utils.js?source#L198)**
682 |
683 | Create a promise that will wait for a while before resolution.
684 |
685 | - **param**: `time` { _Integer_ }
686 |
687 | The unit is millisecond.
688 |
689 | - **param**: `val` { _Any_ }
690 |
691 | What the value this promise will resolve.
692 |
693 | - **return**: { _Promise_ }
694 |
695 | - **example**:
696 |
697 | ```js
698 | var sleep = require('yaku/lib/sleep');
699 | sleep(1000).then(() => console.log('after one second'));
700 | ```
701 |
702 | - ### **[Observable](src/utils.js?source#L204)**
703 |
704 | Read the `Observable` section.
705 |
706 | - **type**: { _Function_ }
707 |
708 | - ### **[retry(countdown, fn, this)](src/utils.js?source#L253)**
709 |
710 | Retry a function until it resolves before a mount of times, or reject with all
711 | the error states.
712 |
713 | - **version_added**:
714 |
715 | v0.7.10
716 |
717 | - **param**: `countdown` { _Number | Function_ }
718 |
719 | How many times to retry before rejection.
720 | When it's a function `(errs) => Boolean | Promise.resolve(Boolean)`,
721 | you can use it to create complex countdown logic,
722 | it can even return a promise to create async countdown logic.
723 |
724 | - **param**: `fn` { _Function_ }
725 |
726 | The function can return a promise or not.
727 |
728 | - **param**: `this` { _Any_ }
729 |
730 | Optional. The context to call the function.
731 |
732 | - **return**: { _Function_ }
733 |
734 | The wrapped function. The function will reject an array
735 | of reasons that throwed by each try.
736 |
737 | - **example**:
738 |
739 | Retry 3 times before rejection.
740 | ```js
741 | var retry = require('yaku/lib/retry');
742 | var { request } = require('nokit');
743 |
744 | retry(3, request)('http://test.com').then(
745 | (body) => console.log(body),
746 | (errs) => console.error(errs)
747 | );
748 | ```
749 |
750 | - **example**:
751 |
752 | Here a more complex retry usage, it shows an random exponential backoff algorithm to
753 | wait and retry again, which means the 10th attempt may take 10 minutes to happen.
754 | ```js
755 | var retry = require('yaku/lib/retry');
756 | var sleep = require('yaku/lib/sleep');
757 | var { request } = require('nokit');
758 |
759 | function countdown (retries) {
760 | var attempt = 0;
761 | return async () => {
762 | var r = Math.random() * Math.pow(2, attempt) * 1000;
763 | var t = Math.min(r, 1000 * 60 * 10);
764 | await sleep(t);
765 | return attempt++ < retries;
766 | };
767 | }
768 |
769 | retry(countdown(10), request)('http://test.com').then(
770 | (body) => console.log(body),
771 | (errs) => console.error(errs)
772 | );
773 | ```
774 |
775 | - ### **[throw(err)](src/utils.js?source#L267)**
776 |
777 | Throw an error to break the program.
778 |
779 | - **param**: `err` { _Any_ }
780 |
781 | - **example**:
782 |
783 | ```js
784 | var ythrow = require('yaku/lib/throw');
785 | Promise.resolve().then(() => {
786 | // This error won't be caught by promise.
787 | ythrow('break the program!');
788 | });
789 | ```
790 |
791 |
792 |
793 |
794 | # Observable
795 |
796 | - ### **[Observable(executor)](src/Observable.js?source#L59)**
797 |
798 | Create a composable observable object.
799 | Promise can't resolve multiple times, this function makes it possible, so
800 | that you can easily map, filter and even back pressure events in a promise way.
801 | For real world example: [Double Click Demo](https://jsbin.com/niwuti/edit?html,js,output).
802 |
803 | - **version_added**:
804 |
805 | v0.7.2
806 |
807 | - **param**: `executor` { _Function_ }
808 |
809 | `(emit) ->` It's optional.
810 |
811 | - **return**: { _Observable_ }
812 |
813 | - **example**:
814 |
815 | ```js
816 | var Observable = require("yaku/lib/Observable");
817 | var linear = new Observable();
818 |
819 | var x = 0;
820 | setInterval(linear.emit, 1000, x++);
821 |
822 | // Wait for a moment then emit the value.
823 | var quad = linear.subscribe(async x => {
824 | await sleep(2000);
825 | return x * x;
826 | });
827 |
828 | var another = linear.subscribe(x => -x);
829 |
830 | quad.subscribe(
831 | value => { console.log(value); },
832 | reason => { console.error(reason); }
833 | );
834 |
835 | // Emit error
836 | linear.emit(Promise.reject(new Error("reason")));
837 |
838 | // Unsubscribe a observable.
839 | quad.unsubscribe();
840 |
841 | // Unsubscribe all subscribers.
842 | linear.subscribers = [];
843 | ```
844 |
845 | - **example**:
846 |
847 | Use it with DOM.
848 | ```js
849 | var filter = fn => v => fn(v) ? v : new Promise(() => {});
850 |
851 | var keyup = new Observable((emit) => {
852 | document.querySelector('input').onkeyup = emit;
853 | });
854 |
855 | var keyupText = keyup.subscribe(e => e.target.value);
856 |
857 | // Now we only get the input when the text length is greater than 3.
858 | var keyupTextGT3 = keyupText.subscribe(filter(text => text.length > 3));
859 |
860 | keyupTextGT3.subscribe(v => console.log(v));
861 | ```
862 |
863 | - ### **[emit(value)](src/Observable.js?source#L74)**
864 |
865 | Emit a value.
866 |
867 | - **param**: `value` { _Any_ }
868 |
869 | - ### **[value](src/Observable.js?source#L80)**
870 |
871 | The promise that will resolve current value.
872 |
873 | - **type**: { _Promise_ }
874 |
875 | - ### **[publisher](src/Observable.js?source#L86)**
876 |
877 | The publisher observable of this.
878 |
879 | - **type**: { _Observable_ }
880 |
881 | - ### **[subscribers](src/Observable.js?source#L92)**
882 |
883 | All the subscribers subscribed this observable.
884 |
885 | - **type**: { _Array_ }
886 |
887 | - ### **[subscribe(onEmit, onError)](src/Observable.js?source#L100)**
888 |
889 | It will create a new Observable, like promise.
890 |
891 | - **param**: `onEmit` { _Function_ }
892 |
893 | - **param**: `onError` { _Function_ }
894 |
895 | - **return**: { _Observable_ }
896 |
897 | - ### **[unsubscribe](src/Observable.js?source#L115)**
898 |
899 | Unsubscribe this.
900 |
901 | - ### **[Observable.merge(iterable)](src/Observable.js?source#L167)**
902 |
903 | Merge multiple observables into one.
904 |
905 | - **version_added**:
906 |
907 | 0.9.6
908 |
909 | - **param**: `iterable` { _Iterable_ }
910 |
911 | - **return**: { _Observable_ }
912 |
913 | - **example**:
914 |
915 | ```js
916 | var Observable = require("yaku/lib/Observable");
917 | var sleep = require("yaku/lib/sleep");
918 |
919 | var src = new Observable(emit => setInterval(emit, 1000, 0));
920 |
921 | var a = src.subscribe(v => v + 1; });
922 | var b = src.subscribe((v) => sleep(10, v + 2));
923 |
924 | var out = Observable.merge([a, b]);
925 |
926 | out.subscribe((v) => {
927 | console.log(v);
928 | })
929 | ```
930 |
931 |
932 |
933 |
934 |
935 | # Unit Test
936 |
937 | This project use [promises-aplus-tests][] to test the compliance of Promises/A+ specification. There are about 900 test cases.
938 |
939 | Use `npm run no -- test` to run the unit test.
940 |
941 |
942 |
943 | # Benchmark
944 |
945 | Use `npm run no -- benchmark` to run the benchmark.
946 |
947 |
948 |
949 | # Contribute
950 |
951 | Other than use `gulp`, all my projects use [nokit][] to deal with automation.
952 | Run `npm run no -- -h` to print all the tasks that defined in the [nofile.js][].
953 | If you installed `nokit` globally, you can just run `no -h` without `npm run` and `--`.
954 |
955 |
956 | [docs/lazyTree.md]: docs/lazyTree.md
957 | [docs/debugHelperComparison.md]: docs/debugHelperComparison.md
958 | [Bluebird]: https://github.com/petkaantonov/bluebird
959 | [ES6-promise]: https://github.com/jakearchibald/es6-promise
960 | [native]: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects
961 | [q]: https://github.com/kriskowal/q
962 | [release page]: https://github.com/ysmood/yaku/releases
963 | [docs/minPromiseA+.coffee]: docs/minPromiseA+.coffee
964 | [promises-aplus-tests]: https://github.com/promises-aplus/promises-tests
965 | [longjohn]: https://github.com/mattinsler/longjohn
966 | [crhome-lst]: http://www.html5rocks.com/en/tutorials/developertools/async-call-stack
967 | [Browserify]: http://browserify.org
968 | [Webpack]: http://webpack.github.io/
969 | [CoffeeScript]: http://coffeescript.org/
970 | [nokit]: https://github.com/ysmood/nokit
971 | [nofile.js]: nofile.js
--------------------------------------------------------------------------------