promise-nodeify
Call a Node-style callback with the resolution value or rejection cause of a Promise without the common pitfalls.
Introductory Example
var promiseNodeify = require('promise-nodeify');
// Function which returns a Promise
function returnsPromise() {
return new Promise(function(resolve, reject) {
resolve(42);
});
}
// Function which takes an optional node-style callback
function takesCallback(callback) {
var promise = returnsPromise();
// if callback is not a function, promise is returned as-is
// otherwise callback will be called when promise is resolved or rejected
// promise will not cause unhandledRejection if callback is a function
return promiseNodeify(promise, callback);
}
Features
The important features of nodeify
as compared to naive implementations:
- Creates
Error
for falsey rejection causes. Since Promises may resolve or reject without providing a value or cause, the callback would have no way to distinguish success from failure. This module ensures the error argument is always truthy, substituting anError
when the rejection cause is falsey (and passing the original value as the.cause
property, as bluebird does). - Exceptions thrown by callback cause
uncaughtException
as they would for other callbacks (unlike passing callback to.then
, which causesunhandledRejection
or swallows them). - The callback handles the promise rejection, preventing
unhandledRejection
(unlike if the promise were ignored and callback invoked directly). - Reduces confusion by only returning a Promise when no callback is given
(as opposed to returning the promise argument, which creates uncertainty
about
unhandledRejection
s and multiple threads of control - or returning the result passing the callback to.then
, which resolves to the callback result).
Behavior Comparison
This module provides similar behavior to several popular promise libraries in a promise-library-agnostic way which only requires the ES6 promise functionality subset. However, these existing implementations differ in subtle ways. A brief comparison:
Behavior | this module | bluebird #asCallback |
es-nodeify | nodeify | then #nodeify |
Un-thenify1 | when.js .bindCallback |
---|---|---|---|---|---|---|---|
returns (with function ) |
undefined |
this Promise 2 |
undefined |
Promise<undefined> |
undefined |
undefined |
when(promise) |
returns (with falsey) | promise |
promise |
promise |
Promise<undefined> |
promise |
undefined with unhandledRejection |
when(promise) |
returns (non-function) | promise |
promise |
undefined with unhandledRejection |
promise |
promise |
undefined with unhandledRejection |
when(promise) with uncaughtException |
callback exception | uncaughtException |
uncaughtException |
unhandledRejection |
uncaughtException |
uncaughtException |
unhandledRejection |
uncaughtException |
falsey cause | Error with .cause |
Error with .cause 3 |
Error |
falsey cause | falsey cause | TypeError |
falsey cause |
reject argument length | 1 | 1 | 1 | 1 | 1 | 1 | 2 |
resolve argument length | 2 | undefined ? 1 : 24 |
2 | 2 | 2 | 2 | 2 |
extra argument | ignored | options5 | ignored | ignored | this of callback |
ignored | ignored |
Notes:
- Un-thenify serves a similar purpose, but wraps the Promise-returning function rather than taking the Promise as an argument.
- Temporarily reverted in https://github.com/petkaantonov/bluebird/issues/151 and restored in https://github.com/petkaantonov/bluebird/issues/168
- In response to https://github.com/petkaantonov/bluebird/issues/434
- In response to https://github.com/petkaantonov/bluebird/issues/170
- Supports the
spread
boolean option to passArray
values as separate arguments tocallback
.
Performance Comparison
These benchmarks were done using the benchmark/index.js
script on an Intel(R) Core(TM) i5-3320M CPU @ 2.60GHz
with Node v4.3.1 on
Linux and the following module versions:
Module | Version |
---|---|
benchmark |
2.1.0 |
bluebird |
3.3.3 |
cli-table |
0.3.1 |
es-nodeify |
1.0.0 |
microtime |
2.0.0 |
native-promise-only |
0.8.1 |
nodeify |
1.0.0 |
pinkie-promise |
2.0.0 |
promise |
7.1.1 |
q |
1.4.1 |
rsvp |
3.2.1 |
unthenify |
1.0.1 |
when |
3.7.7 |
Nodeify Resolved Promise
Performance (in operations per second) of calling nodeify
on a resolved
promise (larger is better):
ops/sec | bluebird | native | npo | pinkie | q | rsvp | then | when |
---|---|---|---|---|---|---|---|---|
bluebird#nodeify | 1,922,721.987 | TypeError | TypeError | TypeError | TypeError | TypeError | TypeError | TypeError |
es-nodeify | 1,345,702.588 | 506,103.345 | 510,887.217 | 534,013.961 | 68,915.816 | 1,974,250.737 | 2,096,468.119 | 1,756,177.934 |
nodeify | 147,481.019 | 251,414.264 | 251,535.145 | 253,880.998 | 58,504.098 | 1,355,812.482 | 1,102,467.756 | 1,160,226.624 |
promiseNodeify | 1,586,092.279 | 481,842.79 | 452,529.247 | 455,657.062 | 66,045.273 | 2,108,607.126 | 2,370,823.723 | 1,942,722.539 |
then#nodeify | 136,716.987 | 202,670.23 | 225,297.257 | 231,042.286 | 56,384.953 | 764,719.55 | 1,320,158.92 | 739,062.155 |
unthenify | 100,638.922 | 79,097.99 | 80,488.25 | 78,298.365 | 40,683.82 | 103,125.162 | 100,618.139 | 101,887.997 |
when.bindCallback | 823.326 | 856.669 | 842.975 | 834.864 | 748.669 | 847.556 | 850.316 | 839.995 |
Nodeify Rejected Promise
Performance (in operations per second) of calling nodeify
on a rejected
promise (larger is better):
ops/sec | bluebird | native | npo | pinkie | q | rsvp | then | when |
---|---|---|---|---|---|---|---|---|
bluebird#nodeify | 1,889,496.469 | TypeError | TypeError | TypeError | TypeError | TypeError | TypeError | TypeError |
es-nodeify | 1,247,981.228 | 520,349.959 | 455,337.77 | 466,964.692 | 64,703.247 | 2,182,281.005 | 2,062,330.035 | 1,889,184.935 |
nodeify | 147,454.87 | 325,956.476 | 326,958.556 | 325,971.637 | 53,878.098 | 1,232,726.201 | 952,338.091 | 926,626.949 |
promiseNodeify | 1,170,756.604 | 465,186.326 | 478,343.59 | 489,024.094 | 62,905.801 | 2,097,277.371 | 1,928,682.943 | 1,497,451.328 |
then#nodeify | 131,588.987 | 241,627.02 | 246,557.24 | 245,427.553 | 49,655.492 | 684,232.864 | 1,178,175.996 | 634,041.464 |
unthenify | 96,359.916 | 82,291.679 | 82,507.055 | 83,324.584 | 38,842.741 | 96,432.332 | 97,113.05 | 99,892.099 |
when.bindCallback | 822.083 | 837.698 | 848.358 | 851.348 | 789.546 | 854.184 | 844.102 | 851.644 |
Installation
NPM
This package can be installed using npm by running:
npm install promise-nodeify
Browser
This package can be installed using bower by running:
bower install promise-nodeify
Without Package Manager
This module is also available with a UMD
loader, both minified and un-minified, in the dist
directory. They
can be downloaded, self-hosted, or loaded from a CDN. To use the RawGit
CDN, use the following (X)HTML:
<script src="https://cdn.rawgit.com/kevinoid/promise-nodeify/v0.1.0/dist/promise-nodeify.min.js"></script>
Recipes
Delegate to Promise.prototype.nodeify
If the behavior differences discussed in the Behavior
Comparison section (and any future differences which
may occur) are not significant to your use case and you are interested in
taking advantage of the potential performance
benefit of the implementation provided by the
promise library, use the .delegated
function:
// Using .delegated delegates to .nodeify on the promise argument when present
var promiseNodeify = require('promise-nodeify').delegated;
function returnsPromise() {
return new Promise(function(resolve, reject) {
resolve(42);
});
}
function takesCallback(callback) {
var promise = returnsPromise();
return promiseNodeify(promise, callback);
}
Polyfill Promise.prototype.nodeify
To polyfill the .nodeify
(or .asCallback
) method for a Promise library,
assign the .nodeifyThis
function to Promise.prototype.nodeify
as follows:
Promise.prototype.nodeify = require('promise-nodeify').nodeifyThis;
function returnsPromise() {
return new Promise(function(resolve, reject) {
resolve(42);
});
}
function takesCallback(callback) {
var promise = returnsPromise();
return promise.nodeify(callback);
}
More examples can be found in the test specifications.
API Docs
For a description of the available functions and their arguments, see the API Documentation.
Contributing
Contributions are welcome and very much appreciated! Please add tests to
cover any changes and ensure npm test
passes.
The dist
files are only updated for releases, so please don't include them
in pull requests.
If the desired change is large, complex, backwards-incompatible, can have significantly differing implementations, or may not be in scope for this project, opening an issue before writing the code can avoid frustration and save a lot of time and effort.
License
This package is available under the terms of the MIT License.