What is yaku?
Yaku is a lightweight and fast Promise library that is fully compatible with ES6 Promises. It aims to provide better performance and smaller size compared to native Promises and other Promise libraries.
What are yaku's main functionalities?
Basic Promise Usage
This demonstrates the basic usage of Yaku to create a new Promise and handle its resolution.
const Yaku = require('yaku');
const promise = new Yaku((resolve, reject) => {
setTimeout(() => resolve('Hello, Yaku!'), 1000);
});
promise.then(value => console.log(value));
Chaining Promises
This demonstrates how to chain multiple `then` calls to handle the resolved value step by step.
const Yaku = require('yaku');
const promise = new Yaku((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
});
promise
.then(value => value + 1)
.then(value => value * 2)
.then(value => console.log(value));
Error Handling
This demonstrates how to handle errors in Yaku Promises using the `catch` method.
const Yaku = require('yaku');
const promise = new Yaku((resolve, reject) => {
setTimeout(() => reject(new Error('Something went wrong')), 1000);
});
promise
.then(value => console.log(value))
.catch(error => console.error(error.message));
Promise.all
This demonstrates how to use `Yaku.all` to wait for multiple Promises to resolve.
const Yaku = require('yaku');
const promise1 = Yaku.resolve(1);
const promise2 = Yaku.resolve(2);
const promise3 = Yaku.resolve(3);
Yaku.all([promise1, promise2, promise3])
.then(values => console.log(values));
Other packages similar to yaku
bluebird
Bluebird is a fully featured Promise library with a focus on performance and additional features not found in native Promises. It offers more utility methods and better error handling compared to Yaku, but it is larger in size.
q
Q is another Promise library that provides a lot of additional functionality such as deferred objects and more advanced control flow. It is more feature-rich compared to Yaku but also comes with a larger footprint.
when
When is a lightweight Promise library that focuses on performance and small size, similar to Yaku. It provides a few more utilities for working with Promises but is generally comparable in terms of performance and size.
Overview
Yaku is full compatible with ES6's native Promise, but much faster, and more error friendly.
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).
It only implements the constructor
and then
. It passed all the tests of promises-aplus-tests.
I am not an optimization freak, I try to keep the source code readable and maintainable.
Premature optimization is the root of all evil. I write this lib to research one of my data structure
ideas: docs/lazyTree.md.
Features
Quick Start
Node.js
npm install yaku
Then:
var Promise = require('yaku');
Browser
Use something like Browserify or Webpack, or download the yaku.js
file from release page.
It supports both AMD
, CMD
and CommonJS
. Raw usage without AMD
, CMD
or CommonJS
:
<script type="text/javascript" src ="yaku.js"></script>
<script>
var Promise = Yaku;
</script>
Change Log
docs/changelog.md
Compare to Other Promise Libs
These comparisons only reflect some limited truth, no one is better than all others on all aspects.
For more details see the benchmark/readme.md. There are tons of Promises/A+ implementations, you can see them here. Only some of the famous ones were tested.
Name | 1ms async task / mem | sync task / mem | Helpers | file size |
---|
Yaku | 257ms / 110MB | 126ms / 80MB | +++ | 3.2KB |
Bluebird v2.9 | 249ms / 102MB | 155ms / 80MB | +++++++ | 73KB |
ES6-promise v2.3 | 427ms / 120MB | 92ms / 78MB | + | 18KB |
native iojs v1.8 | 789ms / 189MB | 605ms / 147MB | + | 0KB |
q v1.3 | 2648ms / 646MB | 2373ms / 580MB | +++ | 24K |
- Helpers: extra methods that help with your promise programming, such as
async flow control helpers, debug helpers. For more details: docs/debugHelperComparison.md.
- 1ms async task:
npm run no -- benchmark
, the smaller the better. - sync task:
npm run no -- benchmark --sync
, the smaller the better.
FAQ
-
catch
on old brwoser (IE7, IE8 etc)?
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() {})
.
-
Will Yaku implement done
, finally
, promisify
, etc?
No. All non-ES6 APIs are only implemented for debugging and testing, which means when you remove Yaku, everything
should work well with ES6 native promise. If you need fancy and magic, go for Bluebird.
-
Better long stack trace support?
Latest Node.js and browsers are already support it. If you enabled it, Yaku will take advantage of it
without much overhead. Such as this library longjohn for Node.js, or this article for Chrome.
-
The name Yaku is weird?
The name yaku
comes from the word 約束(yakusoku)
which means promise.
API
-
This class follows the Promises/A+ and
ES6 spec
with some extra helpers.
-
param: executor
{ Function }
Function object with three arguments resolve, reject and
the promise itself.
The first argument fulfills the promise, the second argument rejects it.
We can call these functions, once our operation is completed.
The this
context of the executor is the promise itself, it can be used to add custom handlers,
such as abort
or progress
helpers.
-
example:
Here's an abort example.
var Promise = require('yaku');
var p = new Promise((resolve, reject) => {
var tmr = setTimeout(resolve, 3000);
this.abort = (reason) => {
clearTimeout(tmr);
reject(reason);
};
});
p.abort(new Error('abort'));
-
example:
Here's a progress example.
var Promise = require('yaku');
var p = new Promise((resolve, reject) => {
var self = this;
var count = 0;
var all = 100;
var tmr = setInterval(() => {
try {
self.progress && self.progress(count, all);
} catch (err) {
reject(err);
}
if (count < all)
count++;
else {
resolve();
clearInterval(tmr);
}
}, 1000);
});
p.progress = (curr, all) => {
console.log(curr, '/', all);
};
-
Appends fulfillment and rejection handlers to the promise,
and returns a new promise resolving to the return value of the called handler.
-
param: onFulfilled
{ Function }
Optional. Called when the Promise is resolved.
-
param: onRejected
{ Function }
Optional. Called when the Promise is rejected.
-
return: { Yaku }
It will return a new Yaku which will resolve or reject after
-
example:
the current Promise.
var Promise = require('yaku');
var p = Promise.resolve(10);
p.then((v) => {
console.log(v);
});
-
The catch()
method returns a Promise and deals with rejected cases only.
It behaves the same as calling Promise.prototype.then(undefined, onRejected)
.
-
param: onRejected
{ Function }
A Function called when the Promise is rejected.
This function has one argument, the rejection reason.
-
return: { Yaku }
A Promise that deals with rejected cases only.
-
example:
var Promise = require('yaku');
var p = Promise.reject(10);
p['catch']((v) => {
console.log(v);
});
-
The Promise.resolve(value)
method returns a Promise object that is resolved with the given value.
If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable,
adopting its eventual state; otherwise the returned promise will be fulfilled with the value.
-
The Promise.reject(reason)
method returns a Promise object that is rejected with the given reason.
-
The Promise.race(iterable)
method returns a promise that resolves or rejects
as soon as one of the promises in the iterable resolves or rejects,
with the value or reason from that promise.
-
param: iterable
{ iterable }
An iterable object, such as an Array.
-
return: { Yaku }
The race function returns a Promise that is settled
the same way as the first passed promise to settle.
It resolves or rejects, whichever happens first.
-
example:
var Promise = require('yaku');
Promise.race([
123,
Promise.resolve(0)
])
.then((value) => {
console.log(value);
});
-
The Promise.all(iterable)
method returns a promise that resolves when
all of the promises in the iterable argument have resolved.
The result is passed as an array of values from all the promises.
If something passed in the iterable array is not a promise,
it's converted to one by Promise.resolve. If any of the passed in promises rejects,
the all Promise immediately rejects with the value of the promise that rejected,
discarding all the other promises whether or not they have resolved.
-
param: iterable
{ iterable }
An iterable object, such as an Array.
-
return: { Yaku }
-
example:
var Promise = require('yaku');
Promise.all([
123,
Promise.resolve(0)
])
.then((values) => {
console.log(values);
});
-
Catch all possibly unhandled rejections. If you want to use specific
format to display the error stack, overwrite it.
If it is set, auto console.error
unhandled rejection will be disabed.
-
param: reason
{ Any }
The rejection reason.
-
param: p
{ Yaku }
The promise that was rejected.
-
example:
var Promise = require('yaku');
Promise.onUnhandledRejection = (reason) => {
console.error(reason);
};
# The console will log an unhandled rejection error message.
Promise.reject('my reason');
# The below won't log the unhandled rejection error message.
Promise.reject('v').catch(() => {});
-
It is used to enable the long stack trace.
Once it is enabled, it can't be reverted.
While it is very helpful in development and testing environments,
it is not recommended to use it in production. It will slow down your
application and waste your memory.
-
Only Node has process.nextTick
function. For browser there are
so many ways to polyfill it. Yaku won't do it for you, instead you
can choose what you prefer. For example, this project
setImmediate.
By default, Yaku will use process.nextTick
on Node, setTimeout
on browser.
-
type: { Function }
-
example:
var Promise = require('yaku');
Promise.nextTick = fn => window.setImmediate(fn);
-
example:
You can even use sync resolution if you really know what you are doing.
var Promise = require('yaku');
Promise.nextTick = fn => fn();
Utils
To use it you have to require it separately: yutils = require 'yaku/lib/utils'
.
If you want to use it in the browser, you have to use browserify
or webpack
.
-
An throttled version of Promise.all
, it runs all the tasks under
a concurrent limitation.
To run tasks sequentially, use utils.flow
.
-
param: limit
{ Int }
The max task to run at a time. It's optional.
Default is Infinity
.
-
param: list
{ Array | Function }
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: saveResults
{ Boolean }
Whether to save each promise's result or
not. Default is true.
-
param: progress
{ Function }
If a task ends, the resolve value will be
passed to this function.
-
return: { Promise }
-
example:
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!'));
-
If a function returns promise, convert it to
node callback style function.
-
Create a jQuery.Deferred
like object.
-
The end symbol.
-
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: fns
{ Function | Array }
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.
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.
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');
-
Check if an object is a promise-like object.
-
param: obj
{ Any }
-
return: { Boolean }
-
Convert a node callback style function to a function that returns
promise when the last callback is not supplied.
-
param: fn
{ Function }
-
param: self
{ Any }
The this
to bind to the fn.
-
return: { Function }
-
example:
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);
});
-
Create a promise that will wait for a while before resolution.
-
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.
-
param: executor
{ Function }
(emit) ->
It's optional.
-
return: { Function }
(value) ->
The fucntion's
members:
{
on: (onEmit, onError) => { },
value: Promise,
children: Array
}
-
example:
var linear = utils.source();
var x = 0;
setInterval(() => {
linear(x++);
}, 1000);
var quad = linear.on(async x => {
await utils.sleep(2000);
return x * x;
});
var another = linear.on(x => -x);
quad.on(
value => { console.log(value); },
reason => { console.error(reason); }
);
linear.children.splice(linear.children.indexOf(quad));
linear.children = [];
-
example:
Use it with DOM.
var filter = fn => v => fn(v) ? v : utils.end();
var keyup = utils.source((emit) => {
document.querySelector('input').onkeyup = emit;
});
var keyupText = keyup.on(e => e.target.value);
var keyupTextGT3 = keyupText.on(filter(text => text.length > 3));
keyupTextGT3(v => console.log(v));
-
example:
Merge two sources into one.
let one = utils.source(emit => setInterval(emit, 100, 'one'));
let two = utils.source(emit => setInterval(emit, 200, 'two'));
let merge = arr => arr.forEach(src => src.on(emit));
let three = merge([one, two]);
three.on(v => console.log(v));
-
Throw an error to break the program.
Unit Test
This project use promises-aplus-tests to test the compliance of Promises/A+ specification. There are about 900 test cases.
Use npm run no -- test
to run the unit test.
Benchmark
Use npm run no -- benchmark
to run the benchmark.
Contribute
Other than use gulp
, all my projects use nokit to deal with automation.
Run npm run no -- -h
to print all the tasks that defined in the nofile.coffee.
If you installed nokit
globally, you can just run no -h
without npm run
and --
.