├── .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 | Yaku 5 | 6 | 7 | 8 | 9 | 10 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/callbackify.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | 3 | module.exports = function (fn, self) { 4 | return function () { 5 | var args, cb, j; 6 | args = 2 <= arguments.length ? 7 | _.slice.call(arguments, 0, j = arguments.length - 1) : 8 | (j = 0, []), cb = arguments[j++]; 9 | 10 | if (!_.isFunction(cb)) { 11 | args.push(cb); 12 | return fn.apply(self, args); 13 | } 14 | if (arguments.length === 1) { 15 | args = [cb]; 16 | cb = null; 17 | } 18 | return fn.apply(self, args).then(function (val) { 19 | return typeof cb === "function" ? cb(null, val) : void 0; 20 | })["catch"](function (err) { 21 | if (cb) { 22 | return cb(err); 23 | } else { 24 | return _.Promise.reject(err); 25 | } 26 | }); 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /test/promises-es6-tests.js: -------------------------------------------------------------------------------- 1 | var promisesES6Tests = require("promises-es6-tests"); 2 | var assert = require("assert"); 3 | var kit = require("nokit"); 4 | 5 | var Promise = require("../src/yaku"); 6 | 7 | module.exports = function (opts) { 8 | var adapter = { 9 | deferred: function () { 10 | var defer; 11 | defer = {}; 12 | defer.promise = new Promise(function (resolve, reject) { 13 | defer.resolve = resolve; 14 | return defer.reject = reject; 15 | }); 16 | return defer; 17 | }, 18 | 19 | defineGlobalPromise: function (global) { 20 | global.Promise = Promise; 21 | global.assert = assert; 22 | }, 23 | 24 | removeGlobalPromise: function (global) { 25 | delete global.Promise; 26 | } 27 | }; 28 | 29 | return kit.promisify(promisesES6Tests)(adapter, opts); 30 | }; 31 | -------------------------------------------------------------------------------- /src/genIterator.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | 3 | // Hack: we don't create new object to pass the newly iterated object. 4 | var $ArrIterContainer = {}; 5 | 6 | var ArrIter = _.extendPrototype(function (arr) { 7 | this.arr = arr; 8 | this.len = arr.length; 9 | }, { 10 | i: 0, 11 | next: function () { 12 | var self = this; 13 | $ArrIterContainer.value = self.arr[self.i++]; 14 | $ArrIterContainer.done = self.i > self.len; 15 | return $ArrIterContainer; 16 | } 17 | }); 18 | 19 | /** 20 | * Generate a iterator 21 | * @param {Any} obj 22 | * @return {Function} 23 | */ 24 | function genIterator (obj) { 25 | if (obj) { 26 | var gen = obj[_.Promise.Symbol.iterator]; 27 | if (_.isFunction(gen)) { 28 | return gen.call(obj); 29 | } 30 | 31 | if (obj instanceof Array) { 32 | return new ArrIter(obj); 33 | } 34 | 35 | if (_.isFunction(obj.next)) { 36 | return obj; 37 | } 38 | } 39 | throw new TypeError("invalid_argument"); 40 | } 41 | 42 | module.exports = genIterator; 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yad Smood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /benchmark/others/this-vs-closure.js: -------------------------------------------------------------------------------- 1 | var A, a, utils; 2 | 3 | utils = require("./utils"); 4 | 5 | A = (function () { 6 | var bar1; 7 | 8 | function A () { 9 | this.count1 = 0; 10 | this.count2 = 0; 11 | } 12 | 13 | bar1 = function (self) { 14 | return self.count1++; 15 | }; 16 | 17 | A.prototype.foo1 = function () { 18 | var self; 19 | self = this; 20 | return (function () { 21 | return (function () { 22 | return bar1(self); 23 | })(); 24 | })(); 25 | }; 26 | 27 | A.prototype.foo2 = function () { 28 | var self; 29 | self = this; 30 | return (function () { 31 | return (function () { 32 | return self.bar2(); 33 | })(); 34 | })(); 35 | }; 36 | 37 | A.prototype.bar2 = function () { 38 | return this.count2++; 39 | }; 40 | 41 | return A; 42 | 43 | })(); 44 | 45 | a = new A; 46 | 47 | utils.run(2, function () { 48 | return a.foo1(); 49 | }); 50 | 51 | console.log(a.count1); 52 | 53 | utils.run(2, function () { 54 | return a.foo2(); 55 | }); 56 | 57 | console.log(a.count2); 58 | 59 | utils.run(2, function () { 60 | return a.foo1(); 61 | }); 62 | 63 | console.log(a.count1); 64 | 65 | utils.run(2, function () { 66 | return a.foo2(); 67 | }); 68 | 69 | console.log(a.count2); 70 | -------------------------------------------------------------------------------- /src/promisify.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | var isFn = _.isFunction; 3 | 4 | module.exports = function (fn, self) { 5 | return function (a, b, c, d, e) { 6 | var len = arguments.length 7 | , args, promise, resolve, reject; 8 | 9 | promise = new _.Promise(function (r, rj) { 10 | resolve = r; 11 | reject = rj; 12 | }); 13 | 14 | function cb (err, val) { 15 | err == null ? resolve(val) : reject(err); 16 | } 17 | 18 | switch (len) { 19 | case 0: fn.call(self, cb); break; 20 | case 1: isFn(a) ? fn.call(self, a) : fn.call(self, a, cb); break; 21 | case 2: isFn(b) ? fn.call(self, a, b) : fn.call(self, a, b, cb); break; 22 | case 3: isFn(c) ? fn.call(self, a, b, c) : fn.call(self, a, b, c, cb); break; 23 | case 4: isFn(d) ? fn.call(self, a, b, c, d) : fn.call(self, a, b, c, d, cb); break; 24 | case 5: isFn(e) ? fn.call(self, a, b, c, d, e) : fn.call(self, a, b, c, d, e, cb); break; 25 | default: 26 | args = new Array(len); 27 | 28 | for (var i = 0; i < len; i++) { 29 | args[i] = arguments[i]; 30 | } 31 | 32 | if (isFn(args[len - 1])) { 33 | return fn.apply(self, args); 34 | } 35 | 36 | args[i] = cb; 37 | fn.apply(self, args); 38 | } 39 | 40 | return promise; 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yaku", 3 | "version": "0.11.4", 4 | "description": "A light-weight ES6 Promises/A+ implementation that doesn't hurt.", 5 | "main": "lib/yaku.js", 6 | "scripts": { 7 | "no": "no", 8 | "test": "no test", 9 | "prepublish": "no clean build" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/ysmood/yaku.git" 14 | }, 15 | "keywords": [ 16 | "light-weight", 17 | "es6", 18 | "promise", 19 | "performance", 20 | "promises", 21 | "promises-a", 22 | "promises-aplus", 23 | "async", 24 | "await", 25 | "deferred", 26 | "deferreds", 27 | "future", 28 | "flow control" 29 | ], 30 | "author": "http://ysmood.org", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/ysmood/yaku/issues" 34 | }, 35 | "homepage": "https://github.com/ysmood/yaku", 36 | "files": [ 37 | "lib" 38 | ], 39 | "devDependencies": { 40 | "bluebird": "2.10.2", 41 | "es6-promise": "3.0.2", 42 | "eslint": "1.7.3", 43 | "junit": "0.8.2", 44 | "mocha": "2.3.4", 45 | "nokit": "0.14.10", 46 | "promises-aplus-tests": "*", 47 | "promises-es6-tests": "*", 48 | "q": "1.4.1", 49 | "uglify-js": "2.5.0", 50 | "webpack": "1.12.2" 51 | }, 52 | "eslintConfig": { 53 | "env": { 54 | "browser": true, 55 | "node": true 56 | }, 57 | "extends": "eslint:recommended", 58 | "rules": { 59 | "indent": [ 60 | 2, 61 | 4 62 | ], 63 | "linebreak-style": [ 64 | 2, 65 | "unix" 66 | ], 67 | "semi": [ 68 | 2, 69 | "always" 70 | ], 71 | "quotes": [ 72 | 2, 73 | "double" 74 | ], 75 | "no-cond-assign": 0, 76 | "no-trailing-spaces": 2, 77 | "space-before-function-paren": [ 78 | 2, 79 | "always" 80 | ], 81 | "eqeqeq": [ 82 | 2, 83 | "allow-null" 84 | ] 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /benchmark/template.js: -------------------------------------------------------------------------------- 1 | var cs, kit; 2 | 3 | kit = require("nokit"); 4 | 5 | cs = kit.require("brush"); 6 | 7 | 8 | /** 9 | * The test will run 10 ^ 5 promises. 10 | * Each promise will resolve after 1ms. 11 | * When all tasks are done, print out how much time it takes. 12 | */ 13 | 14 | module.exports = function (name, Promise) { 15 | var ver = (function () { 16 | try { 17 | return require("../node_modules/" + name + "/package.json").version; 18 | } catch (error) { 19 | return require("../package.json").version; 20 | } 21 | })(); 22 | 23 | var countDown = Math.pow(10, 5); 24 | 25 | function checkEnd () { 26 | if (--countDown) { 27 | return; 28 | } 29 | return logResult(); 30 | } 31 | 32 | function logResult () { 33 | var k, mem, memFormat, resolutionTime, v; 34 | resolutionTime = Date.now() - startResolution; 35 | mem = process.memoryUsage(); 36 | memFormat = []; 37 | for (k in mem) { 38 | v = mem[k]; 39 | memFormat.push(k + " - " + (Math.floor(v / 1024 / 1024)) + "mb"); 40 | } 41 | 42 | return console.log((cs.cyan(name)) 43 | + " v" 44 | + ver 45 | + "\n total: " 46 | + (cs.green(initTime + resolutionTime)) 47 | + "ms\n init: " 48 | + initTime 49 | + "ms\n resolution: " 50 | + resolutionTime 51 | + "ms\n memory: " 52 | + (memFormat.join(" | "))); 53 | } 54 | 55 | function resolver (resolve) { 56 | return setTimeout(resolve, 1); 57 | } 58 | 59 | function asyncTask () { 60 | return new Promise(resolver).then(checkEnd); 61 | } 62 | 63 | var i = countDown; 64 | var initTime; 65 | var initStart = Date.now(); 66 | while (i--) { 67 | asyncTask(); 68 | } 69 | 70 | var startResolution = Date.now(); 71 | return initTime = startResolution - initStart; 72 | }; 73 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | - v0.11 2 | 3 | - **API CHANGE** now `utils.source` is removed, instead `utils.Observable` is added. 4 | - opt: performance 5 | - fix: a bind bug of promisify 6 | 7 | - v0.8.0 8 | 9 | - **API CHANGE** `Yaku.onUnhandledRejection` is removed, instead use a more famous spec 10 | - **API CHANGE** removed `utils.end` 11 | - add: `utils.any` 12 | 13 | - v0.7.12 14 | 15 | - add: now Yaku supports ES6 iterable 16 | - fix: #22 don't overwrite the prototype 17 | - fix: #21 ignore old IE `Error.prototype.stack` 18 | - add: #20 support global rejection event 19 | 20 | - v0.7.11 21 | 22 | - add: `retry` helper. 23 | - add: some function names for reflection. 24 | 25 | - v0.7.7 26 | 27 | - minor: add an unique type flag of Yaku 28 | - opt: performance 29 | 30 | - v0.7.4 31 | 32 | - fix: a multiple resovle bug 33 | 34 | - v0.7.2 35 | 36 | - add: `utils.source` 37 | - fix: a stack info typo 38 | 39 | - v0.4.0 40 | 41 | - **API CHANGE** constructor's context is now promise itself 42 | 43 | - v0.3.9 44 | 45 | - minor changes 46 | 47 | - v0.3.8 48 | 49 | - fix: #10 old IE support 50 | - fix: a bug of utils.throw 51 | - fix: an unhandled rejection bug 52 | 53 | - v0.3.3 54 | 55 | - fix: an unhandled rejection bug when rejects inside a catch 56 | - opt: utils 57 | - add: constructor `self` api 58 | 59 | - v0.3.1 60 | 61 | - add: nextTick api 62 | 63 | - v0.3.0 64 | 65 | - opt: minor change, better performance 66 | - fix: #8 67 | 68 | - v0.2.8 69 | 70 | - fix: error handler bugs of `utils.async` and `utils.flow`. 71 | 72 | - v0.2.7 73 | 74 | - opt: long stack trace 75 | 76 | - v0.2.5 77 | 78 | - add: some useful utils 79 | 80 | - v0.2.3 81 | 82 | - fix: #5 long stack trace bug 83 | 84 | - v0.1.9 85 | 86 | - fix: `__filename` is not defined bug on browser. 87 | 88 | - v0.1.8 89 | 90 | - better error log 91 | 92 | - v0.1.6 93 | 94 | - add: #3 support long stack trace 95 | 96 | - v0.1.5 97 | 98 | - fix: #2 Double unhandled rejection log 99 | 100 | - v0.1.4 101 | 102 | - fix: Yaku.all array type bug 103 | -------------------------------------------------------------------------------- /benchmark/readme.md: -------------------------------------------------------------------------------- 1 | ## Async 2 | ``` 3 | Node v1.8.4 4 | OS darwin 5 | Arch x64 6 | CPU Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz 7 | -------------------------------------------------------------------------------- 8 | yaku v0.3.2 9 | total: 257ms 10 | init: 172ms 11 | resolution: 85ms 12 | memory: rss - 110mb | heapTotal - 93mb | heapUsed - 73mb 13 | bluebird v2.9.34 14 | total: 249ms 15 | init: 146ms 16 | resolution: 103ms 17 | memory: rss - 102mb | heapTotal - 85mb | heapUsed - 57mb 18 | es6-promise v2.3.0 19 | total: 427ms 20 | init: 165ms 21 | resolution: 262ms 22 | memory: rss - 120mb | heapTotal - 101mb | heapUsed - 72mb 23 | native v0.3.2 24 | total: 789ms 25 | init: 545ms 26 | resolution: 244ms 27 | memory: rss - 189mb | heapTotal - 170mb | heapUsed - 147mb 28 | q v1.4.1 29 | total: 2648ms 30 | init: 874ms 31 | resolution: 1774ms 32 | memory: rss - 646mb | heapTotal - 619mb | heapUsed - 601mb 33 | ``` 34 | 35 | ## Sync 36 | 37 | ``` 38 | Node v1.8.4 39 | OS darwin 40 | Arch x64 41 | CPU Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz 42 | -------------------------------------------------------------------------------- 43 | es6-promise v2.3.0 44 | total: 92ms 45 | init: 78ms 46 | resolution: 14ms 47 | memory: rss - 78mb | heapTotal - 61mb | heapUsed - 41mb 48 | yaku v0.3.2 49 | total: 126ms 50 | init: 110ms 51 | resolution: 16ms 52 | memory: rss - 80mb | heapTotal - 63mb | heapUsed - 30mb 53 | bluebird v2.9.34 54 | total: 155ms 55 | init: 123ms 56 | resolution: 32ms 57 | memory: rss - 80mb | heapTotal - 61mb | heapUsed - 37mb 58 | native v0.3.2 59 | total: 605ms 60 | init: 519ms 61 | resolution: 86ms 62 | memory: rss - 147mb | heapTotal - 130mb | heapUsed - 108mb 63 | q v1.4.1 64 | total: 2373ms 65 | init: 1173ms 66 | resolution: 1200ms 67 | memory: rss - 580mb | heapTotal - 555mb | heapUsed - 533mb 68 | ``` -------------------------------------------------------------------------------- /src/async.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | var genIterator = require("./genIterator"); 3 | var isPromise = require("./isPromise"); 4 | 5 | module.exports = function (limit, list, saveResults, progress) { 6 | var resutls, running; 7 | resutls = []; 8 | running = 0; 9 | if (!_.isNumber(limit)) { 10 | progress = saveResults; 11 | saveResults = list; 12 | list = limit; 13 | limit = Infinity; 14 | } 15 | if (saveResults == null) { 16 | saveResults = true; 17 | } 18 | 19 | var iter = genIterator(list); 20 | 21 | return new _.Promise(function (resolve, reject) { 22 | var results, resultIndex = 0; 23 | 24 | function addTask (index) { 25 | var p, task; 26 | 27 | task = iter.next(); 28 | if (task.done) { 29 | if (running === 0) { 30 | allDone(); 31 | } 32 | return false; 33 | } 34 | 35 | if (isPromise(task.value)) { 36 | p = task.value; 37 | } else { 38 | p = _.Promise.resolve(task.value); 39 | } 40 | 41 | running++; 42 | 43 | p.then(function (ret) { 44 | running--; 45 | if (saveResults) { 46 | resutls[index] = ret; 47 | } 48 | if (typeof progress === "function") { 49 | progress(ret); 50 | } 51 | return addTask(resultIndex++); 52 | })["catch"](function (err) { 53 | running--; 54 | return reject(err); 55 | }); 56 | 57 | return true; 58 | } 59 | 60 | function allDone () { 61 | if (saveResults) { 62 | return resolve(resutls); 63 | } else { 64 | return resolve(); 65 | } 66 | } 67 | 68 | var i = limit; 69 | results = []; 70 | while (i--) { 71 | if (!addTask(resultIndex++)) { 72 | break; 73 | } else { 74 | results.push(void 0); 75 | } 76 | } 77 | 78 | return results; 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /benchmark/others/closure-async.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | closure: 46ms 5 | nonClosure: 21ms 6 | */ 7 | var closure; 8 | 9 | closure = function () { 10 | var async, countDown, foo, i, len, list, results; 11 | countDown = Math.pow(10, 5); 12 | list = []; 13 | len = 0; 14 | async = function (fn) { 15 | return list[len++] = fn; 16 | }; 17 | process.on("exit", function () { 18 | return console.timeEnd("closure"); 19 | }); 20 | foo = function () { 21 | var a, b; 22 | a = 1; 23 | b = 2; 24 | async(function () { 25 | a + b; 26 | }); 27 | }; 28 | process.nextTick(function () { 29 | var fn, j, len1, results; 30 | results = []; 31 | for (j = 0, len1 = list.length; j < len1; j++) { 32 | fn = list[j]; 33 | results.push(fn()); 34 | } 35 | return results; 36 | }); 37 | console.time("closure"); 38 | i = countDown; 39 | results = []; 40 | while (i--) { 41 | results.push(foo()); 42 | } 43 | return results; 44 | }; 45 | 46 | // nonClosure = function () { 47 | // var async, bar, c, countDown, foo, len, list, results; 48 | // countDown = Math.pow(10, 5); 49 | // list = []; 50 | // len = 0; 51 | // async = function (fn, a, b) { 52 | // list[len++] = fn; 53 | // list[len++] = a; 54 | // return list[len++] = b; 55 | // }; 56 | // process.on("exit", function () { 57 | // return console.timeEnd("nonClosure"); 58 | // }); 59 | // bar = function (a, b) { 60 | // a + b; 61 | // }; 62 | // foo = function () { 63 | // var a, b; 64 | // a = 1; 65 | // b = 2; 66 | // async(bar, a, b); 67 | // }; 68 | // process.nextTick(function () { 69 | // var i, results; 70 | // i = 0; 71 | // results = []; 72 | // while (i < len) { 73 | // results.push(list[i++](list[i++], list[i++])); 74 | // } 75 | // return results; 76 | // }); 77 | // console.time("nonClosure"); 78 | // c = countDown; 79 | // results = []; 80 | // while (c--) { 81 | // results.push(foo()); 82 | // } 83 | // return results; 84 | // }; 85 | 86 | closure(); 87 | -------------------------------------------------------------------------------- /docs/lazyTree.md: -------------------------------------------------------------------------------- 1 | # Lazy Tree Structure 2 | 3 | > This is how Yaku was piled up. 4 | 5 | The base idea of promise is keeping all data and data handlers inside the promise world, promise will help to box them, proxy them and unbox them. You may have already heard of it before, most people like to call it [Monad][]. But this isn't all, you need to know another interesting idea to have a better understand on what abstraction promise does for you. I call it Lazy Tree, a dynamic tree constructed by promises. 6 | 7 | We are already familiar with the tree structure. In functional programming world, expressions domains the world, everything is just a mathematical concept, no need for space and time. But in the real world, things get dirty, we cannot throw the input into a function, and it magically give us the answer as if there's no side effect. The function will consume memory, the CPU will emit some radiation. Promise is something that glues the space, time and the real world. 8 | 9 | **Rather than laid on space, a Lazy Tree is a tree that laid on both space and time, it's 4 dimensional (x, y, time and err).** 10 | 11 | That's the biggest difference between callback style async flow control and promise. The callback style only define the tree on a 2 dimensional way, append the tree won't grow as time pass by. 12 | 13 | The code is complex, to simplify it, let's see some graphics. 14 | 15 | Here we have defined some promises. Like below: 16 | 17 | ``` 18 | space-time 01 19 | 20 | p0 21 | / \ 22 | p1 p4 23 | / \ 24 | p2 p3 25 | ``` 26 | 27 | If promise p1 resolves another promise p5, the tree structure will change to: 28 | 29 | ``` 30 | space-time 02 31 | 32 | p0 33 | / \ 34 | p1 p4 35 | | 36 | p5 37 | / \ 38 | p2 p3 39 | ``` 40 | 41 | There are some interesting constrains of this data structure: 42 | 43 | - Each individual promise can only have one settled value. 44 | - Each individual promise can only have one callback to pass the settled value. 45 | - Each individual callback can only produce one promise. 46 | - The only way to link two promise is through the callback. 47 | 48 | The tree is dynamically modified on runtime. As the dynamic process goes on, 49 | the tree's any node can be extended to any shape, of course it's append only, 50 | you cannot delete node of this tree. But if we can move faster than light, 51 | the process may be reversible. 52 | 53 | Another interesting thing about promise is that it has a built-in [Maybe Monad][monad], 54 | it's like a wormhole, it's a short-circuit between the rejected node and its children. 55 | Such as on the `space-time 01`, if the p0 rejects, the computation inside its children's 56 | resolvers will be skipped, it won't waste your CPU cycles to reach the other side of the universe. 57 | 58 | So the `rejectors` of promise is the third dimension of the Lazy Tree, it helps us to jump between nodes. 59 | 60 | [monad]: https://en.wikipedia.org/wiki/Monad_(functional_programming) -------------------------------------------------------------------------------- /docs/debugHelperComparison.md: -------------------------------------------------------------------------------- 1 | ### Better unhandled error 2 | 3 | Here the error is actually caught by the handler on `L14`. 4 | Only `Yaku` will not report an `unhandled error`. Even the 5 | native works improperly. 6 | 7 | ```javascript 8 | var Yaku = require('yaku') 9 | var Bluebird = require('bluebird') 10 | 11 | Yaku.enableLongStackTrace() 12 | Bluebird.longStackTraces() 13 | 14 | test = function (Promise) { 15 | p = Promise.resolve().then(function () { 16 | abc() 17 | }) 18 | 19 | p.then(function () {}) 20 | 21 | p.catch(function () {}) // L14 22 | } 23 | 24 | test(Yaku) 25 | test(Bluebird) 26 | test(Promise) // Chrome Native 27 | ``` 28 | 29 | 30 | ### More details for long stack trace 31 | 32 | Only Yaku will trace back to the root the Promise chain. 33 | 34 | ```javascript 35 | var Yaku = require('yaku') 36 | var Bluebird = require('bluebird') 37 | 38 | Yaku.enableLongStackTrace() 39 | Bluebird.longStackTraces() 40 | 41 | test = function (Promise) { 42 | Promise.resolve() // L8 43 | .then(function () { // L9 44 | abc() // L10 45 | }) 46 | } 47 | 48 | test(Yaku) 49 | test(Bluebird) 50 | ``` 51 | 52 | Trace info of Bluebird: 53 | 54 | ``` 55 | Unhandled rejection ReferenceError: abc is not defined 56 | at /Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:10:9 57 | at processImmediate [as _immediateCallback] (timers.js:358:17) 58 | From previous event: 59 | at test (/Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:9:6) 60 | at Object. (/Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:15:1) 61 | at Module._compile (module.js:460:26) 62 | at Object.Module._extensions..js (module.js:478:10) 63 | at Module.load (module.js:355:32) 64 | at Function.Module._load (module.js:310:12) 65 | at Function.Module.runMain (module.js:501:10) 66 | at startup (node.js:129:16) 67 | at node.js:814:3 68 | ``` 69 | 70 | Trace info of Yaku: 71 | 72 | ``` 73 | Unhandled Rejection: ReferenceError: abc is not defined 74 | at /Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:10:9 75 | at Timer.listOnTimeout (timers.js:110:15) 76 | From previous event: 77 | at test (/Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:9:6) 78 | at Object. (/Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:14:1) 79 | at Module._compile (module.js:460:26) 80 | at Object.Module._extensions..js (module.js:478:10) 81 | at Module.load (module.js:355:32) 82 | at Function.Module._load (module.js:310:12) 83 | at Function.Module.runMain (module.js:501:10) 84 | at startup (node.js:129:16) 85 | at node.js:814:3 86 | From previous event: 87 | at test (/Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:8:13) 88 | at Object. (/Users/ys/Cosmos/Cradle/Vane/yaku/test/longStackTrace.js:14:1) 89 | at Module._compile (module.js:460:26) 90 | at Object.Module._extensions..js (module.js:478:10) 91 | at Module.load (module.js:355:32) 92 | at Function.Module._load (module.js:310:12) 93 | at Function.Module.runMain (module.js:501:10) 94 | at startup (node.js:129:16) 95 | at node.js:814:3 96 | ``` -------------------------------------------------------------------------------- /nofile.js: -------------------------------------------------------------------------------- 1 | var kit = require("nokit"); 2 | kit.require("drives"); 3 | 4 | module.exports = function (task, option) { 5 | option("--debug", "run with remote debug server"); 6 | option("--port <8219>", "remote debug server port", 8219); 7 | option("--grep ", "run test that match the pattern", "."); 8 | option("--noAplus", "don't run the promises-aplus-tests"); 9 | option("--sync", "sync benchmark"); 10 | option("--browserPort <8227>", "browser test port", 8227); 11 | 12 | task("default build", ["doc", "code"]); 13 | 14 | task("doc", ["code"], "build doc", function () { 15 | var size; 16 | size = kit.statSync("dist/yaku.min.js").size / 1024; 17 | return kit.warp("src/*.js") 18 | .load(kit.drives.comment2md({ 19 | tpl: "docs/readme.jst.md", 20 | doc: { 21 | size: size.toFixed(1) 22 | } 23 | })).run(); 24 | }); 25 | 26 | function addLicense (str) { 27 | var version; 28 | version = kit.require("./package", __dirname).version; 29 | return ("/*\n Yaku v" + version + 30 | "\n (c) 2015 Yad Smood. http://ysmood.org\n License MIT\n*/\n") 31 | + str; 32 | } 33 | 34 | task("code", ["lint"], "build source code", function () { 35 | return kit.warp("src/*.js").load(function (f) { 36 | if (f.dest.name === "yaku") { 37 | return f.set(addLicense(f.contents)); 38 | } 39 | }).run("lib").then(function () { 40 | kit.mkdirsSync("dist"); 41 | return kit.spawn("uglifyjs", ["-mc", "-o", "dist/yaku.min.js", "lib/yaku.js"]); 42 | }); 43 | }); 44 | 45 | task("lint", "lint js files", function () { 46 | kit.removeSync("{lib,dist}"); 47 | return kit.spawn("eslint", ["src/*.js"]); 48 | }); 49 | 50 | task("all", ["lint"], "bundle all", function () { 51 | process.env.NODE_ENV = "production"; 52 | return kit.spawn("webpack"); 53 | }); 54 | 55 | task("lab l", "run and monitor \"test/lab.js\"", function (opts) { 56 | var args; 57 | args = ["test/lab.js"]; 58 | if (opts.debug) { 59 | kit.log(opts.debug); 60 | args.splice(0, 0, "--nodejs", "--debug-brk=" + opts.port); 61 | } 62 | return kit.monitorApp({ 63 | args: args 64 | }); 65 | }); 66 | 67 | task("test", "run Promises/A+ tests", ["test-yaku", "test-aplus"], true); 68 | 69 | task("test-yaku", "test yaku specs tests", function (opts) { 70 | var junitOpts = ["-g", opts.grep]; 71 | 72 | return kit.spawn("junit", junitOpts.concat([ 73 | "test/basic.js", 74 | "test/unhandledRejection.js"]) 75 | ); 76 | }); 77 | 78 | task("test-aplus", "test aplus tests", require("./test/promises-aplus-tests.js")); 79 | 80 | task("test-es6", "test es6 tests", require("./test/promises-es6-tests.js")); 81 | 82 | task("benchmark", "compare performance between different libraries", function (opts) { 83 | var os, paths, sync; 84 | process.env.NODE_ENV = "production"; 85 | os = require("os"); 86 | 87 | console.log("Node " + process.version + "\nOS " + // eslint-disable-line 88 | (os.platform()) + "\nArch " + (os.arch()) + 89 | "\nCPU " + (os.cpus()[0].model) + 90 | "\n" + (kit._.repeat("-", 80))); 91 | 92 | paths = kit.globSync("benchmark/*.js"); 93 | sync = opts.sync ? "sync" : ""; 94 | return kit.async(paths.map(function (path) { 95 | return function () { 96 | return kit.spawn("node", [path, sync]); 97 | }; 98 | })); 99 | }); 100 | 101 | task("clean", "Clean temp files", function () { 102 | return kit.remove("{.nokit,lib,.nobone}"); 103 | }); 104 | 105 | task("browser", "Unit test on browser", function () { 106 | kit.spawn("webpack", ["--progress", "--watch"]); 107 | }); 108 | }; 109 | -------------------------------------------------------------------------------- /src/Observable.js: -------------------------------------------------------------------------------- 1 | var _ = require("./_"); 2 | var genIterator = require("./genIterator"); 3 | 4 | /** 5 | * Create a composable observable object. 6 | * Promise can't resolve multiple times, this function makes it possible, so 7 | * that you can easily map, filter and even back pressure events in a promise way. 8 | * For real world example: [Double Click Demo](https://jsbin.com/niwuti/edit?html,js,output). 9 | * @version_added v0.7.2 10 | * @param {Function} executor `(emit) ->` It's optional. 11 | * @return {Observable} 12 | * @example 13 | * ```js 14 | * var Observable = require("yaku/lib/Observable"); 15 | * var linear = new Observable(); 16 | * 17 | * var x = 0; 18 | * setInterval(linear.emit, 1000, x++); 19 | * 20 | * // Wait for a moment then emit the value. 21 | * var quad = linear.subscribe(async x => { 22 | * await sleep(2000); 23 | * return x * x; 24 | * }); 25 | * 26 | * var another = linear.subscribe(x => -x); 27 | * 28 | * quad.subscribe( 29 | * value => { console.log(value); }, 30 | * reason => { console.error(reason); } 31 | * ); 32 | * 33 | * // Emit error 34 | * linear.emit(Promise.reject(new Error("reason"))); 35 | * 36 | * // Unsubscribe a observable. 37 | * quad.unsubscribe(); 38 | * 39 | * // Unsubscribe all subscribers. 40 | * linear.subscribers = []; 41 | * ``` 42 | * @example 43 | * Use it with DOM. 44 | * ```js 45 | * var filter = fn => v => fn(v) ? v : new Promise(() => {}); 46 | * 47 | * var keyup = new Observable((emit) => { 48 | * document.querySelector('input').onkeyup = emit; 49 | * }); 50 | * 51 | * var keyupText = keyup.subscribe(e => e.target.value); 52 | * 53 | * // Now we only get the input when the text length is greater than 3. 54 | * var keyupTextGT3 = keyupText.subscribe(filter(text => text.length > 3)); 55 | * 56 | * keyupTextGT3.subscribe(v => console.log(v)); 57 | * ``` 58 | */ 59 | var Observable = module.exports = function Observable (executor) { 60 | var self = this 61 | , emit = genEmit(self); 62 | 63 | self.subscribers = []; 64 | 65 | executor && executor(emit); 66 | }; 67 | 68 | _.extendPrototype(Observable, { 69 | 70 | /** 71 | * Emit a value. 72 | * @param {Any} value 73 | */ 74 | emit: null, 75 | 76 | /** 77 | * The promise that will resolve current value. 78 | * @type {Promise} 79 | */ 80 | value: _.Promise.resolve(), 81 | 82 | /** 83 | * The publisher observable of this. 84 | * @type {Observable} 85 | */ 86 | publisher: null, 87 | 88 | /** 89 | * All the subscribers subscribed this observable. 90 | * @type {Array} 91 | */ 92 | subscribers: null, 93 | 94 | /** 95 | * It will create a new Observable, like promise. 96 | * @param {Function} onEmit 97 | * @param {Function} onError 98 | * @return {Observable} 99 | */ 100 | subscribe: function (onEmit, onError) { 101 | var self = this, subscriber = new Observable(); 102 | subscriber._onEmit = onEmit; 103 | subscriber._onError = onError; 104 | subscriber._nextErr = genNextErr(subscriber.emit); 105 | 106 | subscriber.publisher = self; 107 | self.subscribers.push(subscriber); 108 | 109 | return subscriber; 110 | }, 111 | 112 | /** 113 | * Unsubscribe this. 114 | */ 115 | unsubscribe: function () { 116 | var publisher = this.publisher; 117 | publisher && publisher.subscribers.splice(publisher.subscribers.indexOf(this), 1); 118 | } 119 | 120 | }); 121 | 122 | function genEmit (self) { 123 | return self.emit = function (val) { 124 | var i = 0, len = self.subscribers.length, subscriber; 125 | while (i < len) { 126 | subscriber = self.subscribers[i++]; 127 | self.value = _.Promise.resolve(val); 128 | self.value.then( 129 | subscriber._onEmit, 130 | subscriber._onError 131 | ).then( 132 | subscriber.emit, 133 | subscriber._nextErr 134 | ); 135 | } 136 | }; 137 | } 138 | 139 | function genNextErr (emit) { 140 | return function (reason) { 141 | emit(_.Promise.reject(reason)); 142 | }; 143 | } 144 | 145 | /** 146 | * Merge multiple observables into one. 147 | * @version_added 0.9.6 148 | * @param {Iterable} iterable 149 | * @return {Observable} 150 | * @example 151 | * ```js 152 | * var Observable = require("yaku/lib/Observable"); 153 | * var sleep = require("yaku/lib/sleep"); 154 | * 155 | * var src = new Observable(emit => setInterval(emit, 1000, 0)); 156 | * 157 | * var a = src.subscribe(v => v + 1; }); 158 | * var b = src.subscribe((v) => sleep(10, v + 2)); 159 | * 160 | * var out = Observable.merge([a, b]); 161 | * 162 | * out.subscribe((v) => { 163 | * console.log(v); 164 | * }) 165 | * ``` 166 | */ 167 | Observable.merge = function merge (iterable) { 168 | var iter = genIterator(iterable); 169 | return new Observable(function (emit) { 170 | var item; 171 | 172 | function onError (e) { 173 | emit(_.Promise.reject(e)); 174 | } 175 | 176 | while (!(item = iter.next()).done) { 177 | item.value.subscribe(emit, onError); 178 | } 179 | }); 180 | }; 181 | -------------------------------------------------------------------------------- /docs/minPromiseA+.coffee: -------------------------------------------------------------------------------- 1 | ###* 2 | * Before reading this source file, open web page [Promises/A+](https://promisesaplus.com). 3 | ### 4 | 5 | class Yaku 6 | 7 | ###* 8 | * This class follows the [Promises/A+](https://promisesaplus.com) and 9 | * [ES6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) spec 10 | * with some extra helpers. 11 | * @param {Function} executor Function object with two arguments resolve and reject. 12 | * The first argument fulfills the promise, the second argument rejects it. 13 | * We can call these functions, once our operation is completed. 14 | ### 15 | constructor: (executor) -> 16 | executor genSettler(@, $resolved), genSettler(@, $rejected) 17 | 18 | ###* 19 | * Appends fulfillment and rejection handlers to the promise, 20 | * and returns a new promise resolving to the return value of the called handler. 21 | * @param {Function} onFulfilled Optional. Called when the Promise is resolved. 22 | * @param {Function} onRejected Optional. Called when the Promise is rejected. 23 | * @return {Yaku} It will return a new Yaku which will resolve or reject after 24 | * the current Promise. 25 | ### 26 | then: (onFulfilled, onRejected) -> 27 | addHandler @, new Yaku(->), onFulfilled, onRejected 28 | 29 | # ********************** Private ********************** 30 | 31 | ### 32 | * All static variable name will begin with `$`. Such as `$rejected`. 33 | * @private 34 | ### 35 | 36 | ###* 37 | * These are some static symbolys. 38 | * @private 39 | ### 40 | $rejected = 0 41 | $resolved = 1 42 | $pending = 2 43 | 44 | # Default state 45 | _state: $pending 46 | 47 | ###* 48 | * The number of current promises that attach to this Yaku instance. 49 | * @private 50 | ### 51 | _pCount: 0 52 | 53 | 54 | # *************************** Promise Hepers **************************** 55 | 56 | ###* 57 | * It will produce a settlePromise function to user. 58 | * Such as the resolve and reject in this `new Yaku (resolve, reject) ->`. 59 | * @private 60 | * @param {Yaku} self 61 | * @param {Integer} state The value is one of `$pending`, `$resolved` or `$rejected`. 62 | * @return {Function} `(value) -> undefined` A resolve or reject function. 63 | ### 64 | genSettler = (self, state) -> (value) -> 65 | settlePromise self, state, value 66 | 67 | ###* 68 | * Link the promise1 to the promise2. 69 | * @private 70 | * @param {Yaku} p1 71 | * @param {Yaku} p2 72 | * @param {Function} onFulfilled 73 | * @param {Function} onRejected 74 | ### 75 | addHandler = (p1, p2, onFulfilled, onRejected) -> 76 | # 2.2.1 77 | if typeof onFulfilled == 'function' 78 | p2._onFulfilled = onFulfilled 79 | if typeof onRejected == 'function' 80 | p2._onRejected = onRejected 81 | 82 | # 2.2.6 83 | if p1._state == $pending 84 | p1[p1._pCount++] = p2 85 | else 86 | scheduleHandler p1, p2 87 | 88 | # 2.2.7 89 | p2 90 | 91 | ###* 92 | * Resolve the value returned by onFulfilled or onRejected. 93 | * @private 94 | * @param {Yaku} p1 95 | * @param {Yaku} p2 96 | ### 97 | scheduleHandler = (p1, p2) -> setTimeout -> 98 | handler = if p1._state then p2._onFulfilled else p2._onRejected 99 | 100 | # 2.2.7.3 101 | # 2.2.7.4 102 | if handler == undefined 103 | settlePromise p2, p1._state, p1._value 104 | return 105 | 106 | try 107 | # 2.2.5 108 | x = handler p1._value 109 | catch e 110 | # 2.2.7.2 111 | settlePromise p2, $rejected, e 112 | return 113 | 114 | # 2.2.7.1 115 | settleWithX p2, x 116 | 117 | return 118 | 119 | ###* 120 | * Resolve or reject a promise. 121 | * @param {Yaku} p 122 | * @param {Integer} state 123 | * @param {Any} value 124 | ### 125 | settlePromise = (p, state, value) -> 126 | # 2.1.2 127 | # 2.1.3 128 | return if p._state != $pending 129 | 130 | # 2.1.1.1 131 | p._state = state 132 | p._value = value 133 | 134 | i = 0 135 | len = p._pCount 136 | 137 | # 2.2.2 138 | # 2.2.3 139 | while i < len 140 | # 2.2.4 141 | scheduleHandler p, p[i++] 142 | 143 | p 144 | 145 | ###* 146 | * Resolve or reject primise with value x. The x can also be a thenable. 147 | * @private 148 | * @param {Yaku} p 149 | * @param {Any | Thenable} x A normal value or a thenable. 150 | ### 151 | settleWithX = (p, x) -> 152 | # 2.3.1 153 | if x == p and x 154 | settlePromise p, $rejected, new TypeError 'promise_circular_chain' 155 | return 156 | 157 | # 2.3.2 158 | # 2.3.3 159 | type = typeof x 160 | if x != null and (type == 'function' or type == 'object') 161 | try 162 | # 2.3.2.1 163 | xthen = x.then 164 | catch e 165 | # 2.3.3.2 166 | settlePromise p, $rejected, e 167 | return 168 | 169 | if typeof xthen == 'function' 170 | settleXthen p, x, xthen 171 | else 172 | # 2.3.3.4 173 | settlePromise p, $resolved, x 174 | else 175 | # 2.3.4 176 | settlePromise p, $resolved, x 177 | 178 | p 179 | 180 | ###* 181 | * Resolve then with its promise. 182 | * @private 183 | * @param {Yaku} p 184 | * @param {Thenable} x 185 | * @param {Function} xthen 186 | ### 187 | settleXthen = (p, x, xthen) -> 188 | try 189 | # 2.3.3.3 190 | xthen.call x, (y) -> 191 | # 2.3.3.3.3 192 | return if not x 193 | x = null 194 | 195 | # 2.3.3.3.1 196 | settleWithX p, y 197 | return 198 | , (r) -> 199 | # 2.3.3.3.3 200 | return if not x 201 | x = null 202 | 203 | # 2.3.3.3.2 204 | settlePromise p, $rejected, r 205 | return 206 | catch e 207 | # 2.3.3.3.4.1 208 | if x 209 | # 2.3.3.3.4.2 210 | settlePromise p, $rejected, e 211 | x = null 212 | 213 | return 214 | 215 | module.exports = Yaku 216 | -------------------------------------------------------------------------------- /test/unhandledRejection.js: -------------------------------------------------------------------------------- 1 | var Promise = require("../src/yaku"); 2 | var testSuit = require("./testSuit"); 3 | 4 | 5 | var root = typeof global === "object" ? global : window; 6 | 7 | module.exports = testSuit("unhandledRejection", function (it) { 8 | 9 | var process = root.process; 10 | var $val = { val: "OK" }; 11 | 12 | // Node or Browser 13 | if (process) { 14 | return Promise.resolve() 15 | .then(function () { 16 | 17 | return it("unhandled rejection", { reason: $val, promise: true }, function () { 18 | return new Promise(function (r) { 19 | function handler (reason, promise) { 20 | return r({ reason: reason, promise: typeof promise === "object" }); 21 | } 22 | process.once("unhandledRejection", handler); 23 | return Promise.resolve().then(function () { 24 | return Promise.reject($val); 25 | }); 26 | }); 27 | }); 28 | 29 | }).then(function () { 30 | 31 | return it("no unhandled rejection", $val, function () { 32 | return new Promise(function (resolve, reject) { 33 | function handler () { 34 | process.removeListener("unhandledRejection", handler); 35 | return reject(); 36 | } 37 | process.once("unhandledRejection", handler); 38 | 39 | return Promise.reject()["catch"](function () { 40 | return setTimeout(function () { 41 | return resolve($val); 42 | }, 100); 43 | }); 44 | }); 45 | }); 46 | 47 | }).then(function () { 48 | 49 | return it("unhandled rejection inside a catch", $val, function () { 50 | return new Promise(function (r) { 51 | function handler (reason) { 52 | return r(reason); 53 | } 54 | process.once("unhandledRejection", handler); 55 | 56 | return Promise.reject()["catch"](function () { 57 | return Promise.reject($val); 58 | }); 59 | }); 60 | }); 61 | 62 | }).then(function () { 63 | 64 | return it("unhandled rejection only once", 1, function () { 65 | var count = 0; 66 | function handler () { 67 | return count++; 68 | } 69 | 70 | process.on("unhandledRejection", handler); 71 | 72 | Promise.reject().then(function () { 73 | return $val; 74 | }); 75 | 76 | return new Promise(function (r) { 77 | return setTimeout(function () { 78 | process.removeListener("unhandledRejection", handler); 79 | return r(count); 80 | }, 50); 81 | }); 82 | }); 83 | 84 | }).then(function () { 85 | 86 | return it("long stack trace", 2, function () { 87 | Promise.enableLongStackTrace(); 88 | return Promise.resolve().then(function () { 89 | throw new Error("abc"); 90 | })["catch"](function (err) { 91 | return err.stack.match(/From previous event:/g).length; 92 | }); 93 | }); 94 | 95 | }); 96 | 97 | } else { 98 | return Promise.resolve() 99 | .then(function () { 100 | 101 | return it("unhandled rejection", { reason: $val, promise: true }, function () { 102 | return new Promise(function (r) { 103 | function handler (e) { 104 | root.onunhandledrejection = null; 105 | return r({ reason: e.reason, promise: typeof e.promise === "object" }); 106 | } 107 | root.onunhandledrejection = handler; 108 | return Promise.resolve().then(function () { 109 | return Promise.reject($val); 110 | }); 111 | }); 112 | }); 113 | 114 | }).then(function () { 115 | 116 | return it("no unhandled rejection", $val, function () { 117 | return new Promise(function (resolve, reject) { 118 | function handler () { 119 | root.onunhandledrejection = null; 120 | return reject(); 121 | } 122 | root.onunhandledrejection = handler; 123 | 124 | return Promise.reject()["catch"](function () { 125 | return setTimeout(function () { 126 | return resolve($val); 127 | }, 100); 128 | }); 129 | }); 130 | }); 131 | 132 | }).then(function () { 133 | 134 | return it("unhandled rejection inside a catch", $val, function () { 135 | return new Promise(function (r) { 136 | function handler (e) { 137 | root.onunhandledrejection = null; 138 | return r(e.reason); 139 | } 140 | root.onunhandledrejection = handler; 141 | 142 | return Promise.reject()["catch"](function () { 143 | return Promise.reject($val); 144 | }); 145 | }); 146 | }); 147 | 148 | }).then(function () { 149 | 150 | return it("unhandled rejection only once", 1, function () { 151 | var count = 0; 152 | function handler () { 153 | return count++; 154 | } 155 | 156 | root.onunhandledrejection = handler; 157 | 158 | Promise.reject().then(function () { 159 | return $val; 160 | }); 161 | 162 | return new Promise(function (r) { 163 | return setTimeout(function () { 164 | root.onunhandledrejection = null; 165 | return r(count); 166 | }, 50); 167 | }); 168 | }); 169 | 170 | }).then(function () { 171 | 172 | return it("long stack trace", 2, function () { 173 | Promise.enableLongStackTrace(); 174 | return Promise.resolve().then(function () { 175 | throw new Error("abc"); 176 | })["catch"](function (err) { 177 | return err.stack.match(/From previous event:/g).length; 178 | }); 179 | }); 180 | 181 | }); 182 | } 183 | 184 | }); 185 | -------------------------------------------------------------------------------- /docs/readme.jst.md: -------------------------------------------------------------------------------- 1 | 2 | Promises/A+ logo 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 | [![NPM version](https://badge.fury.io/js/yaku.svg)](http://badge.fury.io/js/yaku) [![Build Status](https://travis-ci.org/ysmood/yaku.svg)](https://travis-ci.org/ysmood/yaku) [![Deps Up to Date](https://david-dm.org/ysmood/yaku.svg?style=flat)](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 | Promises/A+ logo 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 | [![NPM version](https://badge.fury.io/js/yaku.svg)](http://badge.fury.io/js/yaku) [![Build Status](https://travis-ci.org/ysmood/yaku.svg)](https://travis-ci.org/ysmood/yaku) [![Deps Up to Date](https://david-dm.org/ysmood/yaku.svg?style=flat)](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 --------------------------------------------------------------------------------