Comparing version 0.7.7 to 0.7.8
/* | ||
Yaku v0.7.7 | ||
Yaku v0.7.8 | ||
(c) 2015 Yad Smood. http://ysmood.org | ||
License MIT | ||
*/ | ||
"use strict"; | ||
var Promise = require("./yaku"); | ||
/** | ||
* Create a composable event source function. | ||
* Promise can't resolve multiple times, this function makes it possible, so | ||
* that you can easily map, filter and debounce events in a promise way. | ||
* For real world example: [Double Click Demo](https://jsfiddle.net/ysmood/musds0sv/). | ||
* @param {Function} executor `(emit) ->` It's optional. | ||
* @return {Function} `(onEmit, onError) ->` The function's | ||
* members: | ||
* ```js | ||
* { | ||
* emit: (value) => { \/* ... *\/ }, | ||
* | ||
* // Get current value from it. | ||
* value: Promise, | ||
* | ||
* // All the children spawned from current source. | ||
* children: Array | ||
* } | ||
* ``` | ||
* @example | ||
* ```js | ||
* var source = require("yaku/lib/source"); | ||
* var linear = source(); | ||
* | ||
* var x = 0; | ||
* setInterval(() => { | ||
* linear.emit(x++); | ||
* }, 1000); | ||
* | ||
* // Wait for a moment then emit the value. | ||
* var quad = linear(async x => { | ||
* await sleep(2000); | ||
* return x * x; | ||
* }); | ||
* | ||
* var another = linear(x => -x); | ||
* | ||
* quad( | ||
* value => { console.log(value); }, | ||
* reason => { console.error(reason); } | ||
* ); | ||
* | ||
* // Emit error | ||
* linear.emit(Promise.reject("reason")); | ||
* | ||
* // Dispose a specific source. | ||
* linear.children.splice(linear.children.indexOf(quad)); | ||
* | ||
* // Dispose all children. | ||
* linear.children = []; | ||
* ``` | ||
* @example | ||
* Use it with DOM. | ||
* ```js | ||
* var filter = fn => v => fn(v) ? v : new Promise(() => {}); | ||
* | ||
* var keyup = source((emit) => { | ||
* document.querySelector('input').onkeyup = emit; | ||
* }); | ||
* | ||
* var keyupText = keyup(e => e.target.value); | ||
* | ||
* // Now we only get the input when the text length is greater than 3. | ||
* var keyupTextGT3 = keyupText(filter(text => text.length > 3)); | ||
* | ||
* keyupTextGT3(v => console.log(v)); | ||
* ``` | ||
* @example | ||
* Merge two sources into one. | ||
* ```js | ||
* let one = source(emit => setInterval(emit, 100, 'one')); | ||
* let two = source(emit => setInterval(emit, 200, 'two')); | ||
* let merge = arr => arr.forEach(src => src(emit)); | ||
* | ||
* let three = merge([one, two]); | ||
* three(v => console.log(v)); | ||
* ``` | ||
*/ | ||
var source = function (executor) { | ||
module.exports = function source (executor) { | ||
function src (onEmit, onError) { | ||
@@ -124,3 +44,1 @@ var nextSrc = source(); | ||
}; | ||
module.exports = source; |
704
lib/utils.js
/* | ||
Yaku v0.7.7 | ||
Yaku v0.7.8 | ||
(c) 2015 Yad Smood. http://ysmood.org | ||
License MIT | ||
*/ | ||
var Promise, isArray, isFunction, isNumber, utils, | ||
slice = [].slice; | ||
// This file contains all the non-ES6-standard helpers based on promise. | ||
Promise = require('./yaku'); | ||
module.exports = { | ||
isNumber = function(obj) { | ||
return typeof obj === 'number'; | ||
}; | ||
/** | ||
* An throttled version of `Promise.all`, it runs all the tasks under | ||
* a concurrent limitation. | ||
* To run tasks sequentially, use `yaku/lib/flow`. | ||
* @param {Int} limit The max task to run at a time. It's optional. | ||
* Default is `Infinity`. | ||
* @param {Array | Function} list | ||
* If the list is an array, it should be a list of functions or promises, | ||
* and each function will return a promise. | ||
* If the list is a function, it should be a iterator that returns | ||
* a promise, when it returns `yaku/lib/end`, the iteration ends. Of course | ||
* it can never end. | ||
* @param {Boolean} saveResults Whether to save each promise's result or | ||
* not. Default is true. | ||
* @param {Function} progress If a task ends, the resolved value will be | ||
* passed to this function. | ||
* @return {Promise} | ||
* @example | ||
* ```js | ||
* var kit = require('nokit'); | ||
* var async = require('yaku/lib/async'); | ||
* var end = require('yaku/lib/end'); | ||
* | ||
* var urls = [ | ||
* 'http://a.com', | ||
* 'http://b.com', | ||
* 'http://c.com', | ||
* 'http://d.com' | ||
* ]; | ||
* var tasks = [ | ||
* () => kit.request(url[0]), | ||
* () => kit.request(url[1]), | ||
* () => kit.request(url[2]), | ||
* () => kit.request(url[3]) | ||
* ]; | ||
* | ||
* async(tasks).then(() => kit.log('all done!')); | ||
* | ||
* async(2, tasks).then(() => kit.log('max concurrent limit is 2')); | ||
* | ||
* async(3, () => { | ||
* var url = urls.pop(); | ||
* if (url) | ||
* return kit.request(url); | ||
* else | ||
* return end; | ||
* }) | ||
* .then(() => kit.log('all done!')); | ||
* ``` | ||
*/ | ||
async: require('./async'), | ||
isArray = function(obj) { | ||
return obj instanceof Array; | ||
}; | ||
/** | ||
* If a function returns promise, convert it to | ||
* node callback style function. | ||
* @param {Function} fn | ||
* @param {Any} self The `this` to bind to the fn. | ||
* @return {Function} | ||
*/ | ||
callbackify: require('./callbackify'), | ||
isFunction = function(obj) { | ||
return typeof obj === 'function'; | ||
}; | ||
/** | ||
* Create a `jQuery.Deferred` like object. | ||
*/ | ||
Deferred: require('./Deferred'), | ||
utils = module.exports = { | ||
/** | ||
* The end symbol. | ||
* @return {Promise} A promise that will end the current pipeline. | ||
*/ | ||
end: require('./end'), | ||
/** | ||
* An throttled version of `Promise.all`, it runs all the tasks under | ||
* a concurrent limitation. | ||
* To run tasks sequentially, use `utils.flow`. | ||
* @param {Int} limit The max task to run at a time. It's optional. | ||
* Default is `Infinity`. | ||
* @param {Array | Function} list | ||
* If the list is an array, it should be a list of functions or promises, | ||
* and each function will return a promise. | ||
* If the list is a function, it should be a iterator that returns | ||
* a promise, when it returns `utils.end`, the iteration ends. Of course | ||
* it can never end. | ||
* @param {Boolean} saveResults Whether to save each promise's result or | ||
* not. Default is true. | ||
* @param {Function} progress If a task ends, the resolved value will be | ||
* passed to this function. | ||
* @return {Promise} | ||
* @example | ||
* ```js | ||
* var kit = require('nokit'); | ||
* var utils = require('yaku/lib/utils'); | ||
* | ||
* var urls = [ | ||
* 'http://a.com', | ||
* 'http://b.com', | ||
* 'http://c.com', | ||
* 'http://d.com' | ||
* ]; | ||
* var tasks = [ | ||
* () => kit.request(url[0]), | ||
* () => kit.request(url[1]), | ||
* () => kit.request(url[2]), | ||
* () => kit.request(url[3]) | ||
* ]; | ||
* | ||
* utils.async(tasks).then(() => kit.log('all done!')); | ||
* | ||
* utils.async(2, tasks).then(() => kit.log('max concurrent limit is 2')); | ||
* | ||
* utils.async(3, () => { | ||
* var url = urls.pop(); | ||
* if (url) | ||
* return kit.request(url); | ||
* else | ||
* return utils.end; | ||
* }) | ||
* .then(() => kit.log('all done!')); | ||
* ``` | ||
*/ | ||
async: function(limit, list, saveResults, progress) { | ||
var isIterDone, iter, iterIndex, resutls, running; | ||
resutls = []; | ||
running = 0; | ||
isIterDone = false; | ||
iterIndex = 0; | ||
if (!isNumber(limit)) { | ||
progress = saveResults; | ||
saveResults = list; | ||
list = limit; | ||
limit = Infinity; | ||
} | ||
if (saveResults == null) { | ||
saveResults = true; | ||
} | ||
if (isArray(list)) { | ||
iter = function() { | ||
var el; | ||
el = list[iterIndex]; | ||
if (el === void 0) { | ||
return utils.end; | ||
} else if (isFunction(el)) { | ||
return el(); | ||
} else { | ||
return el; | ||
} | ||
}; | ||
} else if (isFunction(list)) { | ||
iter = list; | ||
} else { | ||
throw new TypeError('wrong argument type: ' + list); | ||
} | ||
return new Promise(function(resolve, reject) { | ||
var addTask, allDone, i, results; | ||
addTask = function() { | ||
var index, p, task; | ||
task = iter(); | ||
index = iterIndex++; | ||
if (isIterDone || task === utils.end) { | ||
isIterDone = true; | ||
if (running === 0) { | ||
allDone(); | ||
} | ||
return false; | ||
} | ||
if (utils.isPromise(task)) { | ||
p = task; | ||
} else { | ||
p = Promise.resolve(task); | ||
} | ||
running++; | ||
p.then(function(ret) { | ||
running--; | ||
if (saveResults) { | ||
resutls[index] = ret; | ||
} | ||
if (typeof progress === "function") { | ||
progress(ret); | ||
} | ||
return addTask(); | ||
})["catch"](function(err) { | ||
running--; | ||
return reject(err); | ||
}); | ||
return true; | ||
}; | ||
allDone = function() { | ||
if (saveResults) { | ||
return resolve(resutls); | ||
} else { | ||
return resolve(); | ||
} | ||
}; | ||
i = limit; | ||
results = []; | ||
while (i--) { | ||
if (!addTask()) { | ||
break; | ||
} else { | ||
results.push(void 0); | ||
} | ||
} | ||
return results; | ||
}); | ||
}, | ||
/** | ||
* Creates a function that is the composition of the provided functions. | ||
* Besides, it can also accept async function that returns promise. | ||
* See `yaku/lib/async`, if you need concurrent support. | ||
* @param {Function | Array} fns Functions that return | ||
* promise or any value. | ||
* And the array can also contains promises or values other than function. | ||
* If there's only one argument and it's a function, it will be treated as an iterator, | ||
* when it returns `yaku/lib/end`, the iteration ends. | ||
* @return {Function} `(val) -> Promise` A function that will return a promise. | ||
* @example | ||
* It helps to decouple sequential pipeline code logic. | ||
* ```js | ||
* var kit = require('nokit'); | ||
* var flow = require('yaku/lib/flow'); | ||
* | ||
* function createUrl (name) { | ||
* return "http://test.com/" + name; | ||
* } | ||
* | ||
* function curl (url) { | ||
* return kit.request(url).then((body) => { | ||
* kit.log('get'); | ||
* return body; | ||
* }); | ||
* } | ||
* | ||
* function save (str) { | ||
* kit.outputFile('a.txt', str).then(() => { | ||
* kit.log('saved'); | ||
* }); | ||
* } | ||
* | ||
* var download = flow(createUrl, curl, save); | ||
* // same as "download = flow([createUrl, curl, save])" | ||
* | ||
* download('home'); | ||
* ``` | ||
* @example | ||
* Walk through first link of each page. | ||
* ```js | ||
* var kit = require('nokit'); | ||
* var flow = require('yaku/lib/flow'); | ||
* var end = require('yaku/lib/end'); | ||
* | ||
* var list = []; | ||
* function iter (url) { | ||
* if (!url) return end; | ||
* | ||
* return kit.request(url) | ||
* .then((body) => { | ||
* list.push(body); | ||
* var m = body.match(/href="(.+?)"/); | ||
* if (m) return m[0]; | ||
* }); | ||
* } | ||
* | ||
* var walker = flow(iter); | ||
* walker('test.com'); | ||
* ``` | ||
*/ | ||
flow: require('./flow'), | ||
/** | ||
* If a function returns promise, convert it to | ||
* node callback style function. | ||
* @param {Function} fn | ||
* @param {Any} self The `this` to bind to the fn. | ||
* @return {Function} | ||
*/ | ||
callbackify: function(fn, self) { | ||
return function() { | ||
var args, cb, j; | ||
args = 2 <= arguments.length ? slice.call(arguments, 0, j = arguments.length - 1) : (j = 0, []), cb = arguments[j++]; | ||
if (!isFunction(cb)) { | ||
args.push(cb); | ||
return fn.apply(self, args); | ||
} | ||
if (arguments.length === 1) { | ||
args = [cb]; | ||
cb = null; | ||
} | ||
return fn.apply(self, args).then(function(val) { | ||
return typeof cb === "function" ? cb(null, val) : void 0; | ||
})["catch"](function(err) { | ||
if (cb) { | ||
return cb(err); | ||
} else { | ||
return Promise.reject(err); | ||
} | ||
}); | ||
}; | ||
}, | ||
/** | ||
* Check if an object is a promise-like object. | ||
* @param {Any} obj | ||
* @return {Boolean} | ||
*/ | ||
isPromise: require('./isPromise'), | ||
/** | ||
* Create a `jQuery.Deferred` like object. | ||
*/ | ||
Deferred: function() { | ||
var defer; | ||
defer = {}; | ||
defer.promise = new Promise(function(resolve, reject) { | ||
defer.resolve = resolve; | ||
return defer.reject = reject; | ||
}); | ||
return defer; | ||
}, | ||
/** | ||
* Convert a node callback style function to a function that returns | ||
* promise when the last callback is not supplied. | ||
* @param {Function} fn | ||
* @param {Any} self The `this` to bind to the fn. | ||
* @return {Function} | ||
* @example | ||
* ```js | ||
* var promisify = require('yaku/lib/promisify'); | ||
* function foo (val, cb) { | ||
* setTimeout(() => { | ||
* cb(null, val + 1); | ||
* }); | ||
* } | ||
* | ||
* var bar = promisify(foo); | ||
* | ||
* bar(0).then((val) => { | ||
* console.log val // output => 1 | ||
* }); | ||
* | ||
* // It also supports the callback style. | ||
* bar(0, (err, val) => { | ||
* console.log(val); // output => 1 | ||
* }); | ||
* ``` | ||
*/ | ||
promisify: require('./promisify'), | ||
/** | ||
* The end symbol. | ||
* @return {Promise} A promise that will end the current pipeline. | ||
*/ | ||
end: function() { | ||
return new Promise(function() {}); | ||
}, | ||
/** | ||
* Create a promise that will wait for a while before resolution. | ||
* @param {Integer} time The unit is millisecond. | ||
* @param {Any} val What the value this promise will resolve. | ||
* @return {Promise} | ||
* @example | ||
* ```js | ||
* var sleep = require('yaku/lib/sleep'); | ||
* sleep(1000).then(() => console.log('after one second')); | ||
* ``` | ||
*/ | ||
sleep: require('./sleep'), | ||
/** | ||
* Creates a function that is the composition of the provided functions. | ||
* Besides, it can also accept async function that returns promise. | ||
* See `utils.async`, if you need concurrent support. | ||
* @param {Function | Array} fns Functions that return | ||
* promise or any value. | ||
* And the array can also contains promises or values other than function. | ||
* If there's only one argument and it's a function, it will be treated as an iterator, | ||
* when it returns `utils.end`, the iteration ends. | ||
* @return {Function} `(val) -> Promise` A function that will return a promise. | ||
* @example | ||
* It helps to decouple sequential pipeline code logic. | ||
* ```js | ||
* var kit = require('nokit'); | ||
* var utils = require('yaku/lib/utils'); | ||
* | ||
* function createUrl (name) { | ||
* return "http://test.com/" + name; | ||
* } | ||
* | ||
* function curl (url) { | ||
* return kit.request(url).then((body) => { | ||
* kit.log('get'); | ||
* return body; | ||
* }); | ||
* } | ||
* | ||
* function save (str) { | ||
* kit.outputFile('a.txt', str).then(() => { | ||
* kit.log('saved'); | ||
* }); | ||
* } | ||
* | ||
* var download = utils.flow(createUrl, curl, save); | ||
* // same as "download = utils.flow([createUrl, curl, save])" | ||
* | ||
* download('home'); | ||
* ``` | ||
* @example | ||
* Walk through first link of each page. | ||
* ```js | ||
* var kit = require('nokit'); | ||
* var utils = require('yaku/lib/utils'); | ||
* | ||
* var list = []; | ||
* function iter (url) { | ||
* if (!url) return utils.end; | ||
* | ||
* return kit.request(url) | ||
* .then((body) => { | ||
* list.push(body); | ||
* var m = body.match(/href="(.+?)"/); | ||
* if (m) return m[0]; | ||
* }); | ||
* } | ||
* | ||
* var walker = utils.flow(iter); | ||
* walker('test.com'); | ||
* ``` | ||
*/ | ||
flow: function() { | ||
var fns; | ||
fns = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
return function(val) { | ||
var genIter, iter, run; | ||
genIter = function(arr) { | ||
var iterIndex; | ||
iterIndex = 0; | ||
return function(val) { | ||
var fn; | ||
fn = arr[iterIndex++]; | ||
if (fn === void 0) { | ||
return utils.end; | ||
} else if (isFunction(fn)) { | ||
return fn(val); | ||
} else { | ||
return fn; | ||
} | ||
}; | ||
}; | ||
if (isArray(fns[0])) { | ||
iter = genIter(fns[0]); | ||
} else if (fns.length === 1 && isFunction(fns[0])) { | ||
iter = fns[0]; | ||
} else if (fns.length > 1) { | ||
iter = genIter(fns); | ||
} else { | ||
throw new TypeError('wrong argument type: ' + fn); | ||
} | ||
run = function(preFn) { | ||
return preFn.then(function(val) { | ||
var fn; | ||
fn = iter(val); | ||
if (fn === utils.end) { | ||
return val; | ||
} | ||
return run(utils.isPromise(fn) ? fn : isFunction(fn) ? Promise.resolve(fn(val)) : Promise.resolve(fn)); | ||
}); | ||
}; | ||
return run(Promise.resolve(val)); | ||
}; | ||
}, | ||
/** | ||
* Create a composable event source function. | ||
* Promise can't resolve multiple times, this function makes it possible, so | ||
* that you can easily map, filter and debounce events in a promise way. | ||
* For real world example: [Double Click Demo](https://jsfiddle.net/ysmood/musds0sv/). | ||
* @param {Function} executor `(emit) ->` It's optional. | ||
* @return {Function} `(onEmit, onError) ->` The function's | ||
* members: | ||
* ```js | ||
* { | ||
* emit: (value) => { \/* ... *\/ }, | ||
* | ||
* // Get current value from it. | ||
* value: Promise, | ||
* | ||
* // All the children spawned from current source. | ||
* children: Array | ||
* } | ||
* ``` | ||
* @example | ||
* ```js | ||
* var source = require("yaku/lib/source"); | ||
* var linear = source(); | ||
* | ||
* var x = 0; | ||
* setInterval(() => { | ||
* linear.emit(x++); | ||
* }, 1000); | ||
* | ||
* // Wait for a moment then emit the value. | ||
* var quad = linear(async x => { | ||
* await sleep(2000); | ||
* return x * x; | ||
* }); | ||
* | ||
* var another = linear(x => -x); | ||
* | ||
* quad( | ||
* value => { console.log(value); }, | ||
* reason => { console.error(reason); } | ||
* ); | ||
* | ||
* // Emit error | ||
* linear.emit(Promise.reject("reason")); | ||
* | ||
* // Dispose a specific source. | ||
* linear.children.splice(linear.children.indexOf(quad)); | ||
* | ||
* // Dispose all children. | ||
* linear.children = []; | ||
* ``` | ||
* @example | ||
* Use it with DOM. | ||
* ```js | ||
* var filter = fn => v => fn(v) ? v : new Promise(() => {}); | ||
* | ||
* var keyup = source((emit) => { | ||
* document.querySelector('input').onkeyup = emit; | ||
* }); | ||
* | ||
* var keyupText = keyup(e => e.target.value); | ||
* | ||
* // Now we only get the input when the text length is greater than 3. | ||
* var keyupTextGT3 = keyupText(filter(text => text.length > 3)); | ||
* | ||
* keyupTextGT3(v => console.log(v)); | ||
* ``` | ||
* @example | ||
* Merge two sources into one. | ||
* ```js | ||
* let one = source(emit => setInterval(emit, 100, 'one')); | ||
* let two = source(emit => setInterval(emit, 200, 'two')); | ||
* let merge = arr => arr.forEach(src => src(emit)); | ||
* | ||
* let three = merge([one, two]); | ||
* three(v => console.log(v)); | ||
* ``` | ||
*/ | ||
source: require('./source'), | ||
/** | ||
* Check if an object is a promise-like object. | ||
* @param {Any} obj | ||
* @return {Boolean} | ||
*/ | ||
isPromise: function(obj) { | ||
return obj && isFunction(obj.then); | ||
}, | ||
/** | ||
* Retry a async task until it resolves a mount of times. | ||
* @param {Number | Function} countdown How many times to retry before rejection. | ||
* When it's a function `(errs) => Boolean | Promise.resolve(Boolean)`, | ||
* you can use it to create complex countdown logic, | ||
* it can even return a promise to create async countdown logic. | ||
* @param {Function} fn The function can return a promise or not. | ||
* @param {this} Optional. The context to call the function. | ||
* @return {Function} The wrapped function. The function will reject an array | ||
* of reasons that throwed by each try. | ||
* @example | ||
* Retry 3 times before rejection. | ||
* ```js | ||
* var retry = require('yaku/lib/retry'); | ||
* var { request } = require('nokit'); | ||
* | ||
* retry(3, request)('http://test.com').then( | ||
* (body) => console.log(body), | ||
* (errs) => console.error(errs) | ||
* ); | ||
* ``` | ||
* @example | ||
* Here a more complex retry usage, it shows an random exponential backoff algorithm to | ||
* wait and retry again, which means the 10th attempt may take 10 minutes to happen. | ||
* ```js | ||
* var retry = require('yaku/lib/retry'); | ||
* var sleep = require('yaku/lib/sleep'); | ||
* var { request } = require('nokit'); | ||
* | ||
* function countdown (retries) { | ||
* var attempt = 0; | ||
* return async () => { | ||
* var r = Math.random() * Math.pow(2, attempt) * 1000; | ||
* var t = Math.min(r, 1000 * 60 * 10); | ||
* await sleep(t); | ||
* attempt++ < retries; | ||
* }; | ||
* } | ||
* | ||
* retry(countdown(10), request)('http://test.com').then( | ||
* (body) => console.log(body), | ||
* (errs) => console.error(errs) | ||
* ); | ||
* ``` | ||
*/ | ||
retry: require('./retry'), | ||
/** | ||
* Convert a node callback style function to a function that returns | ||
* promise when the last callback is not supplied. | ||
* @param {Function} fn | ||
* @param {Any} self The `this` to bind to the fn. | ||
* @return {Function} | ||
* @example | ||
* ```js | ||
* function foo (val, cb) { | ||
* setTimeout(() => { | ||
* cb(null, val + 1); | ||
* }); | ||
* } | ||
* | ||
* var bar = utils.promisify(foo); | ||
* | ||
* bar(0).then((val) => { | ||
* console.log val // output => 1 | ||
* }); | ||
* | ||
* // It also supports the callback style. | ||
* bar(0, (err, val) => { | ||
* console.log(val); // output => 1 | ||
* }); | ||
* ``` | ||
*/ | ||
promisify: function(fn, self) { | ||
return function() { | ||
var args; | ||
args = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
if (isFunction(args[args.length - 1])) { | ||
return fn.apply(self, args); | ||
} | ||
return new Promise(function(resolve, reject) { | ||
args.push(function() { | ||
if (arguments[0] != null) { | ||
return reject(arguments[0]); | ||
} else { | ||
return resolve(arguments[1]); | ||
} | ||
}); | ||
return fn.apply(self, args); | ||
}); | ||
}; | ||
}, | ||
/** | ||
* Create a promise that will wait for a while before resolution. | ||
* @param {Integer} time The unit is millisecond. | ||
* @param {Any} val What the value this promise will resolve. | ||
* @return {Promise} | ||
* @example | ||
* ```js | ||
* utils.sleep(1000).then(() => console.log('after one second')); | ||
* ``` | ||
*/ | ||
sleep: function(time, val) { | ||
return new Promise(function(r) { | ||
return setTimeout((function() { | ||
return r(val); | ||
}), time); | ||
}); | ||
}, | ||
/** | ||
* Throw an error to break the program. | ||
* @param {Any} err | ||
* @example | ||
* ```js | ||
* Promise.resolve().then(() => { | ||
* // This error won't be caught by promise. | ||
* utils.throw('break the program!'); | ||
* }); | ||
* ``` | ||
*/ | ||
"throw": function(err) { | ||
setTimeout(function() { | ||
throw err; | ||
}); | ||
} | ||
/** | ||
* Throw an error to break the program. | ||
* @param {Any} err | ||
* @example | ||
* ```js | ||
* var ythrow = require('yaku/lib/throw'); | ||
* Promise.resolve().then(() => { | ||
* // This error won't be caught by promise. | ||
* ythrow('break the program!'); | ||
* }); | ||
* ``` | ||
*/ | ||
"throw": require('./throw') | ||
}; |
/* | ||
Yaku v0.7.7 | ||
Yaku v0.7.8 | ||
(c) 2015 Yad Smood. http://ysmood.org | ||
@@ -20,3 +20,3 @@ License MIT | ||
, $fromPrevious = "From previous event:", | ||
, $fromPrevious = "From previous event:"; | ||
@@ -76,3 +76,3 @@ /** | ||
*/ | ||
Yaku = function (executor) { | ||
var Yaku = module.exports = function (executor) { | ||
var self = this, | ||
@@ -335,3 +335,3 @@ err; | ||
// ********************** Private ********************** | ||
// ********************** Private ********************** | ||
@@ -461,5 +461,3 @@ /** | ||
var scheduleHandler = genScheduler(999, function (p1, p2) { | ||
var x | ||
, p2 | ||
, handler; | ||
var x, handler; | ||
@@ -486,13 +484,8 @@ // 2.2.2 | ||
settleWithX(p2, x); | ||
}) | ||
}); | ||
// Why are there two "genScheduler"s? | ||
// Well, to support the babel's es7 async-await polyfill, I have to hack it. | ||
, scheduleUnhandledRejection = genScheduler( | ||
9, | ||
genScheduler(9, function (p) { | ||
if (!hashOnRejected(p)) | ||
Yaku.onUnhandledRejection(p._value, p); | ||
}) | ||
); | ||
var scheduleUnhandledRejection = genScheduler(9, function (p) { | ||
if (!hashOnRejected(p)) | ||
Yaku.onUnhandledRejection(p._value, p); | ||
}); | ||
@@ -747,12 +740,2 @@ function isYaku (val) { return val && val._Yaku; } | ||
// CMD & AMD Support | ||
try { | ||
module.exports = Yaku; | ||
} catch (e) { | ||
try { | ||
define(function () { return Yaku; }); // eslint-disable-line | ||
} catch (ee) { | ||
root.Yaku = Yaku; | ||
} | ||
} | ||
})(); |
{ | ||
"name": "yaku", | ||
"version": "0.7.7", | ||
"description": "An ES6 Promises/A+ implementation that doesn't hurt.", | ||
"version": "0.7.8", | ||
"description": "A light-weight ES6 Promises/A+ implementation that doesn't hurt.", | ||
"main": "lib/yaku.js", | ||
@@ -42,3 +42,3 @@ "scripts": { | ||
"devDependencies": { | ||
"bluebird": "2.10.0", | ||
"bluebird": "2.10.1", | ||
"coffee-loader": "0.7.2", | ||
@@ -48,4 +48,4 @@ "coffee-script": "1.10.0", | ||
"es6-promise": "3.0.2", | ||
"eslint": "1.5.0", | ||
"nokit": "0.14.2", | ||
"eslint": "1.5.1", | ||
"nokit": "0.14.7", | ||
"promises-aplus-tests": "*", | ||
@@ -59,12 +59,32 @@ "q": "1.4.1", | ||
"browser": true, | ||
"node": true, | ||
"es6": true | ||
"node": true | ||
}, | ||
"extends": "eslint:recommended", | ||
"rules": { | ||
"curly": 0, | ||
"no-use-before-define": 0, | ||
"no-underscore-dangle": 0, | ||
"no-path-concat": 0 | ||
"indent": [ | ||
2, | ||
4 | ||
], | ||
"linebreak-style": [ | ||
2, | ||
"unix" | ||
], | ||
"semi": [ | ||
2, | ||
"always" | ||
], | ||
"no-trailing-spaces": 2, | ||
"space-before-function-paren": [ | ||
2, | ||
"always" | ||
], | ||
"eqeqeq": [ | ||
2, | ||
"allow-null" | ||
], | ||
"no-console": [ | ||
0 | ||
] | ||
} | ||
} | ||
} |
247
readme.md
@@ -18,5 +18,7 @@ <a href="http://promisesaplus.com/"> | ||
# Features | ||
- The minified file is only 3.2KB (1.5KB gzipped) ([Bluebird][] / 73KB, [ES6-promise][] / 18KB) | ||
- The minified file is only 3.1KB (1.5KB gzipped) ([Bluebird][] / 73KB, [ES6-promise][] / 18KB) | ||
- [Better "possibly unhandled rejection" and "long stack trace"][docs/debugHelperComparison.md] than [Bluebird][] | ||
@@ -28,2 +30,4 @@ - Much better performance than the native Promise | ||
# Quick Start | ||
@@ -42,2 +46,4 @@ | ||
## Browser | ||
@@ -56,2 +62,4 @@ | ||
# Change Log | ||
@@ -61,2 +69,4 @@ | ||
# Compare to Other Promise Libs | ||
@@ -69,3 +79,3 @@ | ||
| -------------------- | -------------------- | --------------- | ------- | --------- | | ||
| Yaku | 257ms / 110MB | 126ms / 80MB | +++ | 3.2KB | | ||
| Yaku | 257ms / 110MB | 126ms / 80MB | +++ | 3.1KB | | ||
| [Bluebird][] v2.9 | 249ms / 102MB | 155ms / 80MB | +++++++ | 73KB | | ||
@@ -81,2 +91,4 @@ | [ES6-promise][] v2.3 | 427ms / 120MB | 92ms / 78MB | + | 18KB | | ||
# FAQ | ||
@@ -106,2 +118,4 @@ | ||
# API | ||
@@ -391,12 +405,14 @@ | ||
# Utils | ||
To use it you have to require it separately: `var yutils = require("yaku/lib/utils")`. | ||
If you want to use it in the browser, you have to use `browserify` or `webpack`. | ||
It's a bundle of all the following functions. You can require them all with `var yutils = require("yaku/lib/utils")`, | ||
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`. | ||
- ### **[async(limit, list, saveResults, progress)](src/utils.coffee?source#L63)** | ||
- ### **[async(limit, list, saveResults, progress)](src/utils.js?source#L55)** | ||
An throttled version of `Promise.all`, it runs all the tasks under | ||
a concurrent limitation. | ||
To run tasks sequentially, use `utils.flow`. | ||
To run tasks sequentially, use `yaku/lib/flow`. | ||
@@ -413,3 +429,3 @@ - **<u>param</u>**: `limit` { _Int_ } | ||
If the list is a function, it should be a iterator that returns | ||
a promise, when it returns `utils.end`, the iteration ends. Of course | ||
a promise, when it returns `yaku/lib/end`, the iteration ends. Of course | ||
it can never end. | ||
@@ -433,27 +449,28 @@ | ||
var kit = require('nokit'); | ||
var utils = require('yaku/lib/utils'); | ||
var async = require('yaku/lib/async'); | ||
var end = require('yaku/lib/end'); | ||
var urls = [ | ||
'http://a.com', | ||
'http://b.com', | ||
'http://c.com', | ||
'http://d.com' | ||
'http://a.com', | ||
'http://b.com', | ||
'http://c.com', | ||
'http://d.com' | ||
]; | ||
var tasks = [ | ||
() => kit.request(url[0]), | ||
() => kit.request(url[1]), | ||
() => kit.request(url[2]), | ||
() => kit.request(url[3]) | ||
() => kit.request(url[0]), | ||
() => kit.request(url[1]), | ||
() => kit.request(url[2]), | ||
() => kit.request(url[3]) | ||
]; | ||
utils.async(tasks).then(() => kit.log('all done!')); | ||
async(tasks).then(() => kit.log('all done!')); | ||
utils.async(2, tasks).then(() => kit.log('max concurrent limit is 2')); | ||
async(2, tasks).then(() => kit.log('max concurrent limit is 2')); | ||
utils.async(3, () => { | ||
var url = urls.pop(); | ||
if (url) | ||
return kit.request(url); | ||
else | ||
return utils.end; | ||
async(3, () => { | ||
var url = urls.pop(); | ||
if (url) | ||
return kit.request(url); | ||
else | ||
return end; | ||
}) | ||
@@ -463,3 +480,3 @@ .then(() => kit.log('all done!')); | ||
- ### **[callbackify(fn, self)](src/utils.coffee?source#L137)** | ||
- ### **[callbackify(fn, self)](src/utils.js?source#L64)** | ||
@@ -477,7 +494,7 @@ If a function returns promise, convert it to | ||
- ### **[Deferred](src/utils.coffee?source#L159)** | ||
- ### **[Deferred](src/utils.js?source#L69)** | ||
Create a `jQuery.Deferred` like object. | ||
- ### **[end()](src/utils.coffee?source#L172)** | ||
- ### **[end()](src/utils.js?source#L75)** | ||
@@ -490,7 +507,7 @@ The end symbol. | ||
- ### **[flow(fns)](src/utils.coffee?source#L234)** | ||
- ### **[flow(fns)](src/utils.js?source#L138)** | ||
Creates a function that is the composition of the provided functions. | ||
Besides, it can also accept async function that returns promise. | ||
See `utils.async`, if you need concurrent support. | ||
See `yaku/lib/async`, if you need concurrent support. | ||
@@ -503,3 +520,3 @@ - **<u>param</u>**: `fns` { _Function | Array_ } | ||
If there's only one argument and it's a function, it will be treated as an iterator, | ||
when it returns `utils.end`, the iteration ends. | ||
when it returns `yaku/lib/end`, the iteration ends. | ||
@@ -515,23 +532,23 @@ - **<u>return</u>**: { _Function_ } | ||
var kit = require('nokit'); | ||
var utils = require('yaku/lib/utils'); | ||
var flow = require('yaku/lib/flow'); | ||
function createUrl (name) { | ||
return "http://test.com/" + name; | ||
return "http://test.com/" + name; | ||
} | ||
function curl (url) { | ||
return kit.request(url).then((body) => { | ||
kit.log('get'); | ||
return body; | ||
}); | ||
return kit.request(url).then((body) => { | ||
kit.log('get'); | ||
return body; | ||
}); | ||
} | ||
function save (str) { | ||
kit.outputFile('a.txt', str).then(() => { | ||
kit.log('saved'); | ||
}); | ||
kit.outputFile('a.txt', str).then(() => { | ||
kit.log('saved'); | ||
}); | ||
} | ||
var download = utils.flow(createUrl, curl, save); | ||
// same as "download = utils.flow([createUrl, curl, save])" | ||
var download = flow(createUrl, curl, save); | ||
// same as "download = flow([createUrl, curl, save])" | ||
@@ -546,21 +563,22 @@ download('home'); | ||
var kit = require('nokit'); | ||
var utils = require('yaku/lib/utils'); | ||
var flow = require('yaku/lib/flow'); | ||
var end = require('yaku/lib/end'); | ||
var list = []; | ||
function iter (url) { | ||
if (!url) return utils.end; | ||
if (!url) return end; | ||
return kit.request(url) | ||
.then((body) => { | ||
list.push(body); | ||
var m = body.match(/href="(.+?)"/); | ||
if (m) return m[0]; | ||
}); | ||
return kit.request(url) | ||
.then((body) => { | ||
list.push(body); | ||
var m = body.match(/href="(.+?)"/); | ||
if (m) return m[0]; | ||
}); | ||
} | ||
var walker = utils.flow(iter); | ||
var walker = flow(iter); | ||
walker('test.com'); | ||
``` | ||
- ### **[isPromise(obj)](src/utils.coffee?source#L275)** | ||
- ### **[isPromise(obj)](src/utils.js?source#L145)** | ||
@@ -573,3 +591,3 @@ Check if an object is a promise-like object. | ||
- ### **[promisify(fn, self)](src/utils.coffee?source#L304)** | ||
- ### **[promisify(fn, self)](src/utils.js?source#L174)** | ||
@@ -590,12 +608,13 @@ Convert a node callback style function to a function that returns | ||
```js | ||
var promisify = require('yaku/lib/promisify'); | ||
function foo (val, cb) { | ||
setTimeout(() => { | ||
cb(null, val + 1); | ||
}); | ||
setTimeout(() => { | ||
cb(null, val + 1); | ||
}); | ||
} | ||
var bar = utils.promisify(foo); | ||
var bar = promisify(foo); | ||
bar(0).then((val) => { | ||
console.log val // output => 1 | ||
console.log val // output => 1 | ||
}); | ||
@@ -605,7 +624,7 @@ | ||
bar(0, (err, val) => { | ||
console.log(val); // output => 1 | ||
console.log(val); // output => 1 | ||
}); | ||
``` | ||
- ### **[sleep(time, val)](src/utils.coffee?source#L327)** | ||
- ### **[sleep(time, val)](src/utils.js?source#L187)** | ||
@@ -627,28 +646,8 @@ Create a promise that will wait for a while before resolution. | ||
```js | ||
utils.sleep(1000).then(() => console.log('after one second')); | ||
var sleep = require('yaku/lib/sleep'); | ||
sleep(1000).then(() => console.log('after one second')); | ||
``` | ||
- ### **[throw(err)](src/utils.coffee?source#L342)** | ||
- ### **[source(executor)](src/utils.js?source#L267)** | ||
Throw an error to break the program. | ||
- **<u>param</u>**: `err` { _Any_ } | ||
- **<u>example</u>**: | ||
```js | ||
Promise.resolve().then(() => { | ||
// This error won't be caught by promise. | ||
utils.throw('break the program!'); | ||
}); | ||
``` | ||
# Source | ||
To use it you have to require it separately: `var ysource = require("yaku/lib/source")`. | ||
- ### **[source(executor)](src/source.js?source#L83)** | ||
Create a composable event source function. | ||
@@ -743,4 +742,84 @@ Promise can't resolve multiple times, this function makes it possible, so | ||
- ### **[retry(countdown, fn, Optional)](src/utils.js?source#L314)** | ||
Retry a async task until it resolves a mount of times. | ||
- **<u>param</u>**: `countdown` { _Number | Function_ } | ||
How many times to retry before rejection. | ||
When it's a function `(errs) => Boolean | Promise.resolve(Boolean)`, | ||
you can use it to create complex countdown logic, | ||
it can even return a promise to create async countdown logic. | ||
- **<u>param</u>**: `fn` { _Function_ } | ||
The function can return a promise or not. | ||
- **<u>param</u>**: `Optional` { _this_ } | ||
. The context to call the function. | ||
- **<u>return</u>**: { _Function_ } | ||
The wrapped function. The function will reject an array | ||
of reasons that throwed by each try. | ||
- **<u>example</u>**: | ||
Retry 3 times before rejection. | ||
```js | ||
var retry = require('yaku/lib/retry'); | ||
var { request } = require('nokit'); | ||
retry(3, request)('http://test.com').then( | ||
(body) => console.log(body), | ||
(errs) => console.error(errs) | ||
); | ||
``` | ||
- **<u>example</u>**: | ||
Here a more complex retry usage, it shows an random exponential backoff algorithm to | ||
wait and retry again, which means the 10th attempt may take 10 minutes to happen. | ||
```js | ||
var retry = require('yaku/lib/retry'); | ||
var sleep = require('yaku/lib/sleep'); | ||
var { request } = require('nokit'); | ||
function countdown (retries) { | ||
var attempt = 0; | ||
return async () => { | ||
var r = Math.random() * Math.pow(2, attempt) * 1000; | ||
var t = Math.min(r, 1000 * 60 * 10); | ||
await sleep(t); | ||
attempt++ < retries; | ||
}; | ||
} | ||
retry(countdown(10), request)('http://test.com').then( | ||
(body) => console.log(body), | ||
(errs) => console.error(errs) | ||
); | ||
``` | ||
- ### **[throw(err)](src/utils.js?source#L328)** | ||
Throw an error to break the program. | ||
- **<u>param</u>**: `err` { _Any_ } | ||
- **<u>example</u>**: | ||
```js | ||
var ythrow = require('yaku/lib/throw'); | ||
Promise.resolve().then(() => { | ||
// This error won't be caught by promise. | ||
ythrow('break the program!'); | ||
}); | ||
``` | ||
# Unit Test | ||
@@ -752,2 +831,4 @@ | ||
# Benchmark | ||
@@ -757,2 +838,4 @@ | ||
# Contribute | ||
@@ -759,0 +842,0 @@ |
839
59857
996