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 yaku.aplus. Without comments, it is only 80 lines of code (gzipped size is 0.5KB).
It only implements the constructor
and then
.
Yaku passed all the tests of promises-aplus-tests, promises-es6-tests, and even the core-js tests.
I am not an optimization freak, I try to keep the source code readable and maintainable.
I write this lib to research one of my data structure ideas: docs/lazyTree.md.
Features
- One of the best for mobile, gzipped file is only 2.0KB
- Supports "uncaught rejection" and "long stack trace", Comparison
- Works on IE5+ and other major browsers
- 100% statement and branch test coverage
- Better CPU and memory performance than the native Promise
- Well commented source code with every Promises/A+ spec
- Highly modularized extra helpers, no pollution to its pure ES6 implementation
- Supports ES7
finally
- Composable observable helper
Quick Start
Node.js
npm install yaku
Then:
var Promise = require('yaku');
Or if you don't want any extra debug helper, ES6 only version is here:
var Promise = require('yaku/lib/yaku.core');
Or if you only want aplus support:
var Promise = require('yaku/lib/yaku.aplus');
Browser
Raw usage:
<script type="text/javascript" src="https://raw.githubusercontent.com/ysmood/yaku/master/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.
There are tons of Promises/A+ implementations, you can see them here. Only some of the famous ones were tested.
You can reproduce it on your own machine with npm run no -- benchmark
.
Date: Wed Aug 15 2018 23:06:02 GMT+0900 (Japan Standard Time)
Node v10.8.0
OS darwin
Arch x64
CPU Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz
name | unit tests | coverage | 1ms async task | optional helpers | helpers | gzip |
---|
yaku@0.19.1 | ✓ | 100% 100% | 274ms / 112MB | ✓ | 35 | 1.9KB |
yaku.core@0.19.1 | ✓ | 100% 100% | 292ms / 110MB | ✓ | 29 | 1.6KB |
yaku.aplus@0.19.1 | x (91 failed) | 100% 100% | 328ms / 126MB | ✓ | 7 | 0.5KB |
bluebird@3.5.1 | x (33 failed) | 99% 96% | 183ms / 88MB | partial | 103 | 16.1KB |
es6-promise@4.2.4 | x (48 failed) | ? ? | 510ms / 106MB | x | 13 | 2.4KB |
pinkie@2.0.4 | x (45 failed) | ? ? | 284ms / 138MB | v | 10 | 1.2KB |
native@10.8.0 | ✓ | ? ? | 133ms / 86MB | x | 11 | 0KB |
core-js@2.5.7 | x (5 failed) | ? ? | 239ms / 99MB | x | 13 | 4.8KB |
es6-shim@0.35.3 | ✓ | ? ? | 161ms / 87MB | x | 11 | 15.6KB |
q@1.5.1 | x (21 failed) | ? ? | 896ms / 326MB | x | 74 | 4.7KB |
my-promise@1.1.0 | x (10 failed) | ? ? | 583ms / 224MB | x | 10 | 4KB |
-
unit test: promises-aplus-tests, promises-es6-tests, and even the core-js tests.
-
coverage: statement coverage and branch coverage.
-
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 (total time / memory rss).
-
promises-es6-tests: If you want to test bluebird
against promises-es6-tests,
run npm run no -- test-es6 --shim bluebird
.
-
optional helpers: Whether the helpers can be imported separately or not,
which means you can load the lib without helpers. Such as the bluebird-core
, it will inevitably load
some nonstandard helpers: spread
, promisify
, etc.
FAQ
-
catch
on old browsers (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() {})
.
-
When using with Babel and Regenerator, the unhandled rejection doesn't work.
Because Regenerator use global Promise directly and don't have an api to set the Promise lib.
You have to import Yaku globally to make it use Yaku: require("yaku/lib/global");
.
Unhandled Rejection
Yaku will report any unhandled rejection via console.error
by default, in case you forget to write catch
.
You can catch them manually:
- Browser:
window.onunhandledrejection = ({ promise, reason }) => { /* Your Code */ };
- Node:
process.on("unhandledRejection", (reason, promise) => { /* Your Code */ });
For more spec read Unhandled Rejection Tracking Browser Events.
API
-
require('yaku')
-
require('yaku/lib/utils') or any of them like require('yaku/lib/retry')
- all(limit, list)
- any(iterable)
- async(gen)
- callbackify(fn, self)
- Deferred
- flow(list)
- guard(type, onRejected)
- hash(dict)
- if(cond, trueFn, falseFn)
- isPromise(obj)
- never()
- promisify(fn, self)
- sleep(time, val)
- Observable
- retry(countdown, span, fn, this)
- throw(err)
- timeout(promise, time, reason)
-
require('yaku/lib/Observable')
-
This class follows the Promises/A+ and
ES6 spec
with some extra helpers.
-
param: executor
{ Function }
Function object with two arguments resolve, reject.
The first argument fulfills the promise, the second argument rejects it.
We can call these functions, once our operation is completed.
-
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(new Error("ERR"));
p['catch']((v) => {
console.log(v);
});
-
Register a callback to be invoked when a promise is settled (either fulfilled or rejected).
Similar with the try-catch-finally, it's often used for cleanup.
-
param: onFinally
{ Function }
A Function called when the Promise is settled.
It will not receive any argument.
-
return: { Yaku }
A Promise that will reject if onFinally throws an error or returns a rejected promise.
Else it will resolve previous promise's final state (either fulfilled or rejected).
-
example:
var Promise = require('yaku');
var p = Math.random() > 0.5 ? Promise.resolve() : Promise.reject();
p.finally(() => {
console.log('finally');
});
-
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);
});
-
example:
Use with iterable.
var Promise = require('yaku');
Promise.all((function * () {
yield 10;
yield new Promise(function (r) { setTimeout(r, 1000, "OK") });
})())
.then((values) => {
console.log(values);
});
-
The Promise.allSettled(iterable)
method returns a promise that resolves after all
of the given promises have either resolved or rejected, with an array of objects that
each describes the outcome of each promise.
-
param: iterable
{ iterable }
An iterable object, such as an Array.
-
return: { Yaku }
A promise resolves a list of objects. For each object, a status string is present.
If the status is fulfilled, then a value is present. If the status is rejected, then a reason is present.
The value (or reason) reflects what value each promise was fulfilled (or rejected) with.
-
example:
var Promise = require('yaku');
Promise.allSettled([
Promise.resolve(3),
new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'))
])
.then((values) => {
console.log(values);
});
-
The ES6 Symbol object that Yaku should use, by default it will use the
global one.
-
Use this api to custom the species behavior.
https://tc39.github.io/ecma262/#sec-speciesconstructor
-
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 disabled.
-
Emitted whenever a Promise was rejected and an error handler was
attached to it (for example with ["catch"]()
) later than after an event loop turn.
-
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
application and eat up memory.
It will add an extra property longStack
to the Error object.
-
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
next-tick.
By default, Yaku will use process.nextTick
on Node, setTimeout
on browser.
-
type: { Function }
-
example:
var Promise = require('yaku');
Promise.nextTick = require('next-tick');
-
example:
You can even use sync resolution if you really know what you are doing.
var Promise = require('yaku');
Promise.nextTick = fn => fn();
Utils
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
. You can even use another Promise lib, such as:
require("yaku/lib/_").Promise = require("bluebird");
var source = require("yaku/lib/source");
-
A function that helps run functions under a concurrent limitation.
To run functions sequentially, use yaku/lib/flow
.
-
param: limit
{ Int }
The max task to run at a time. It's optional.
Default is Infinity
.
-
param: list
{ Iterable }
Any iterable object. It should be a lazy iteralbe object,
don't pass in a normal Array with promises.
-
return: { Promise }
-
example:
var kit = require('nokit');
var all = require('yaku/lib/all');
var urls = [
'http://a.com',
'http://b.com',
'http://c.com',
'http://d.com'
];
var tasks = function * () {
var i = 0;
yield kit.request(url[i++]);
yield kit.request(url[i++]);
yield kit.request(url[i++]);
yield kit.request(url[i++]);
}();
all(tasks).then(() => kit.log('all done!'));
all(2, tasks).then(() => kit.log('max concurrent limit is 2'));
all(3, { next: () => {
var url = urls.pop();
return {
done: !url,
value: url && kit.request(url)
};
} })
.then(() => kit.log('all done!'));
-
Similar with the Promise.race
, but only rejects when every entry rejects.
-
param: iterable
{ iterable }
An iterable object, such as an Array.
-
return: { Yaku }
-
example:
var any = require('yaku/lib/any');
any([
123,
Promise.resolve(0),
Promise.reject(new Error("ERR"))
])
.then((value) => {
console.log(value);
});
-
Generator based async/await wrapper.
-
If a function returns promise, convert it to
node callback style function.
-
deprecate Create a jQuery.Deferred
like object.
It will cause some buggy problems, please don't use it.
-
Creates a function that is the composition of the provided functions.
See yaku/lib/async
, if you need concurrent support.
-
param: list
{ Iterable }
Any iterable object. It should be a lazy iteralbe object,
don't pass in a normal Array with promises.
-
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 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);
download('home');
-
example:
Walk through first link of each page.
var kit = require('nokit');
var flow = require('yaku/lib/flow');
var list = [];
function iter (url) {
return {
done: !url,
value: url && 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');
-
Enable a helper to catch specific error type.
It will be directly attach to the prototype of the promise.
-
param: type
{ class }
-
param: onRejected
{ Function }
-
return: { Promise }
var Promise = require('yaku');
require('yaku/lib/guard');
class AnError extends Error {
}
Promise.reject(new AnError('hey'))
.guard(AnError, (err) => {
console.log(err);
})
.then(() => {
console.log('done');
})
.guard(Error, (err) => {
console.log(err)
});
-
Just like Promise.all
, but you pass in a key-value dict,
after every value is resolved, it will resolve a k-v dict.
-
param: dict
{ Object }
-
return: { Promise }
var hash = require('yaku/lib/hash');
var sleep = require('yaku/lib/sleep');
hash({
a: sleep(100, 'a'),
b: sleep(200, 'b')
}).then((dict) => {
console.log(dict.a, dict.b)
})
-
if-else helper
-
param: cond
{ Promise }
-
param: trueFn
{ Function }
-
param: falseFn
{ Function }
-
return: { Promise }
-
example:
var Promise = require('yaku');
var yutils = require('yaku/lib/utils');
yutils.if(Promise.resolve(false), () => {
}, () => {
})
-
deprecate Check if an object is a promise-like object.
Don't use it to coercive a value to Promise, instead use Promise.resolve
.
-
param: obj
{ Any }
-
return: { Boolean }
-
Create a promise that never ends.
-
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:
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
});
bar(0, (err, val) => {
console.log(val);
});
-
Create a promise that will wait for a while before resolution.
-
Read the Observable
section.
-
Retry a function until it resolves before a mount of times, or reject with all
the error states.
-
version_added:
v0.7.10
-
param: countdown
{ Number | Function }
How many times to retry before rejection.
-
param: span
{ Number }
Optional. How long to wait before each retry in millisecond.
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: fn
{ Function }
The function can return a promise or not.
-
param: this
{ Any }
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, wait 1 second before each retry.
var retry = require('yaku/lib/retry');
var { request } = require('nokit');
retry(3, 1000, 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.
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);
return attempt++ < retries;
};
}
retry(countdown(10), request)('http://test.com').then(
(body) => console.log(body),
(errs) => console.error(errs)
);
-
Throw an error to break the program.
-
Create a promise that will reject after a while if the passed in promise
doesn't settle first.
-
param: promise
{ Promise }
The passed promise to wait.
-
param: time
{ Integer }
The unit is millisecond.
-
param: reason
{ Any }
After time out, it will be the reject reason.
-
return: { Promise }
-
example:
var sleep = require('yaku/lib/sleep');
var timeout = require('yaku/lib/timeout');
timeout(sleep(500), 100)["catch"]((err) => {
console.error(err);
});
Observable
-
Create a composable observable object.
Promise can't resolve multiple times, this class makes it possible, so
that you can easily map, filter and even back pressure events in a promise way.
For live example: Double Click Demo.
-
version_added:
v0.7.2
-
param: executor
{ Function }
(next) ->
It's optional.
-
return: { Observable }
-
example:
var Observable = require("yaku/lib/Observable");
var linear = new Observable();
var x = 0;
setInterval(linear.next, 1000, x++);
var quad = linear.subscribe(async x => {
await sleep(2000);
return x * x;
});
var another = linear.subscribe(x => -x);
quad.subscribe(
value => { console.log(value); },
reason => { console.error(reason); }
);
linear.error(new Error("reason"));
quad.unsubscribe();
linear.subscribers = [];
-
example:
Use it with DOM.
var filter = fn => v => fn(v) ? v : new Promise(() => {});
var keyup = new Observable((next) => {
document.querySelector('input').onkeyup = next;
});
var keyupText = keyup.subscribe(e => e.target.value);
var keyupTextGT3 = keyupText.subscribe(filter(text => text.length > 3));
keyupTextGT3.subscribe(v => console.log(v));
-
Emit a value.
-
Emit an error.
-
The publisher observable of this.
-
All the subscribers subscribed this observable.
-
It will create a new Observable, like promise.
-
Unsubscribe this.
-
Merge multiple observables into one.
-
version_added:
0.9.6
-
param: iterable
{ Iterable }
-
return: { Observable }
-
example:
var Observable = require("yaku/lib/Observable");
var sleep = require("yaku/lib/sleep");
var src = new Observable(next => setInterval(next, 1000, 0));
var a = src.subscribe(v => v + 1; });
var b = src.subscribe((v) => sleep(10, v + 2));
var out = Observable.merge([a, b]);
out.subscribe((v) => {
console.log(v);
})
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 against yaku.
Test other libs
basic test
To test bluebird
: npm run no -- test-basic --shim bluebird
The bluebird
can be replaced with other lib, see the test/getPromise.js
for which libs are supported.
aplus test
To test bluebird
: npm run no -- test-aplus --shim bluebird
The bluebird
can be replaced with other lib, see the test/getPromise.js
for which libs are supported.
es6 test
To test bluebird
: npm run no -- test-es6 --shim bluebird
The bluebird
can be replaced with other lib, see the test/getPromise.js
for which libs are supported.
Benchmark
Use npm run no -- benchmark
to run the benchmark.
async/await generator wrapper
Node v5.6.0
OS darwin
Arch x64
CPU Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
yaku: 117ms
co: 283ms
bluebird: 643ms
Contribution
Make sure you have npm
and npm install
at the root of the project first.
Other than use gulp
, all my projects use nokit to deal with automation.
Run npm run no -- -h
to print all the tasks that you can use.
Update readme.md
Please don't alter the readme.md
directly, it is compiled from the docs/readme.jst.md
.
Edit the docs/readme.jst.md
and execute npm run no
to rebuild the project.