Home

Awesome

promise-nodeify

Build Status Coverage Dependency Status Supported Node Version Version on NPM

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:

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:

Behaviorthis modulebluebird #asCallbackes-nodeifynodeifythen #nodeifyUn-thenify<sup>1</sup>when.js .bindCallback
returns (with function)undefinedthis Promise<sup>2</sup>undefinedPromise<undefined>undefinedundefinedwhen(promise)
returns (with falsey)promisepromisepromisePromise<undefined>promiseundefined with unhandledRejectionwhen(promise)
returns (non-function)promisepromiseundefined with unhandledRejectionpromisepromiseundefined with unhandledRejectionwhen(promise) with uncaughtException
callback exceptionuncaughtExceptionuncaughtExceptionunhandledRejectionuncaughtExceptionuncaughtExceptionunhandledRejectionuncaughtException
falsey causeError with .causeError with .cause<sup>3</sup>Errorfalsey causefalsey causeTypeErrorfalsey cause
reject argument length1111112
resolve argument length2undefined ? 1 : 2<sup>4</sup>22222
extra argumentignoredoptions<sup>5</sup>ignoredignoredthis of callbackignoredignored

Notes:

  1. <a id="note-1" name="note-2" /> Un-thenify serves a similar purpose, but wraps the Promise-returning function rather than taking the Promise as an argument.
  2. <a id="note-2" name="note-3" /> Temporarily reverted in https://github.com/petkaantonov/bluebird/issues/151 and restored in https://github.com/petkaantonov/bluebird/issues/168
  3. <a id="note-3" name="note-4" /> In response to https://github.com/petkaantonov/bluebird/issues/434
  4. <a id="note-4" name="note-5" /> In response to https://github.com/petkaantonov/bluebird/issues/170
  5. <a id="note-5" name="note-6" /> Supports the spread boolean option to pass Array values as separate arguments to callback.

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:

ModuleVersion
benchmark2.1.0
bluebird3.3.3
cli-table0.3.1
es-nodeify1.0.0
microtime2.0.0
native-promise-only0.8.1
nodeify1.0.0
pinkie-promise2.0.0
promise7.1.1
q1.4.1
rsvp3.2.1
unthenify1.0.1
when3.7.7

Nodeify Resolved Promise

Performance (in operations per second) of calling nodeify on a resolved promise (larger is better):

ops/secbluebirdnativenpopinkieqrsvpthenwhen
bluebird#nodeify1,922,721.987TypeErrorTypeErrorTypeErrorTypeErrorTypeErrorTypeErrorTypeError
es-nodeify1,345,702.588506,103.345510,887.217534,013.96168,915.8161,974,250.7372,096,468.1191,756,177.934
nodeify147,481.019251,414.264251,535.145253,880.99858,504.0981,355,812.4821,102,467.7561,160,226.624
promiseNodeify1,586,092.279481,842.79452,529.247455,657.06266,045.2732,108,607.1262,370,823.7231,942,722.539
then#nodeify136,716.987202,670.23225,297.257231,042.28656,384.953764,719.551,320,158.92739,062.155
unthenify100,638.92279,097.9980,488.2578,298.36540,683.82103,125.162100,618.139101,887.997
when.bindCallback823.326856.669842.975834.864748.669847.556850.316839.995
<!-- GitHub sanitizes my attempts to make this toggleable. Hide for now. bluebird#nodeify with bluebird x 1,922,722 ops/sec ±1.71% (80 runs sampled) bluebird#nodeify with npo: TypeError: this._then is not a function bluebird#nodeify with pinkie: TypeError: this._then is not a function bluebird#nodeify with q: TypeError: this._then is not a function bluebird#nodeify with rsvp: TypeError: this._then is not a function bluebird#nodeify with then: TypeError: this._then is not a function bluebird#nodeify with when: TypeError: this._then is not a function bluebird#nodeify with native: TypeError: this._then is not a function es-nodeify with bluebird x 1,345,703 ops/sec ±0.84% (81 runs sampled) es-nodeify with npo x 510,887 ops/sec ±1.32% (85 runs sampled) es-nodeify with pinkie x 534,014 ops/sec ±0.92% (84 runs sampled) es-nodeify with q x 68,916 ops/sec ±2.99% (70 runs sampled) es-nodeify with rsvp x 1,974,251 ops/sec ±0.84% (85 runs sampled) es-nodeify with then x 2,096,468 ops/sec ±0.77% (84 runs sampled) es-nodeify with when x 1,756,178 ops/sec ±3.48% (82 runs sampled) es-nodeify with native x 506,103 ops/sec ±1.36% (87 runs sampled) nodeify with bluebird x 147,481 ops/sec ±2.80% (71 runs sampled) nodeify with npo x 251,535 ops/sec ±2.60% (77 runs sampled) nodeify with pinkie x 253,881 ops/sec ±2.53% (67 runs sampled) nodeify with q x 58,504 ops/sec ±4.30% (67 runs sampled) nodeify with rsvp x 1,355,812 ops/sec ±2.98% (81 runs sampled) nodeify with then x 1,102,468 ops/sec ±2.09% (81 runs sampled) nodeify with when x 1,160,227 ops/sec ±3.95% (79 runs sampled) nodeify with native x 251,414 ops/sec ±3.02% (77 runs sampled) promiseNodeify with bluebird x 1,586,092 ops/sec ±1.94% (81 runs sampled) promiseNodeify with npo x 452,529 ops/sec ±0.52% (84 runs sampled) promiseNodeify with pinkie x 455,657 ops/sec ±0.50% (85 runs sampled) promiseNodeify with q x 66,045 ops/sec ±4.14% (71 runs sampled) promiseNodeify with rsvp x 2,108,607 ops/sec ±3.66% (81 runs sampled) promiseNodeify with then x 2,370,824 ops/sec ±1.08% (82 runs sampled) promiseNodeify with when x 1,942,723 ops/sec ±0.79% (86 runs sampled) promiseNodeify with native x 481,843 ops/sec ±0.41% (87 runs sampled) then#nodeify with bluebird x 136,717 ops/sec ±1.83% (82 runs sampled) then#nodeify with npo x 225,297 ops/sec ±2.92% (81 runs sampled) then#nodeify with pinkie x 231,042 ops/sec ±2.14% (80 runs sampled) then#nodeify with q x 56,385 ops/sec ±3.88% (75 runs sampled) then#nodeify with rsvp x 764,720 ops/sec ±3.37% (81 runs sampled) then#nodeify with then x 1,320,159 ops/sec ±2.67% (81 runs sampled) then#nodeify with when x 739,062 ops/sec ±0.78% (85 runs sampled) then#nodeify with native x 202,670 ops/sec ±6.14% (74 runs sampled) unthenify with bluebird x 100,639 ops/sec ±2.24% (84 runs sampled) unthenify with npo x 80,488 ops/sec ±2.78% (84 runs sampled) unthenify with pinkie x 78,298 ops/sec ±3.08% (81 runs sampled) unthenify with q x 40,684 ops/sec ±3.94% (73 runs sampled) unthenify with rsvp x 103,125 ops/sec ±0.78% (84 runs sampled) unthenify with then x 100,618 ops/sec ±3.56% (83 runs sampled) unthenify with when x 101,888 ops/sec ±0.46% (82 runs sampled) unthenify with native x 79,098 ops/sec ±3.09% (83 runs sampled) when.bindCallback with bluebird x 823 ops/sec ±1.21% (76 runs sampled) when.bindCallback with npo x 843 ops/sec ±0.96% (75 runs sampled) when.bindCallback with pinkie x 835 ops/sec ±0.97% (75 runs sampled) when.bindCallback with q x 749 ops/sec ±1.55% (73 runs sampled) when.bindCallback with rsvp x 848 ops/sec ±0.94% (77 runs sampled) when.bindCallback with then x 850 ops/sec ±0.86% (76 runs sampled) when.bindCallback with when x 840 ops/sec ±0.86% (76 runs sampled) when.bindCallback with native x 857 ops/sec ±1.41% (38 runs sampled) -->

Nodeify Rejected Promise

Performance (in operations per second) of calling nodeify on a rejected promise (larger is better):

ops/secbluebirdnativenpopinkieqrsvpthenwhen
bluebird#nodeify1,889,496.469TypeErrorTypeErrorTypeErrorTypeErrorTypeErrorTypeErrorTypeError
es-nodeify1,247,981.228520,349.959455,337.77466,964.69264,703.2472,182,281.0052,062,330.0351,889,184.935
nodeify147,454.87325,956.476326,958.556325,971.63753,878.0981,232,726.201952,338.091926,626.949
promiseNodeify1,170,756.604465,186.326478,343.59489,024.09462,905.8012,097,277.3711,928,682.9431,497,451.328
then#nodeify131,588.987241,627.02246,557.24245,427.55349,655.492684,232.8641,178,175.996634,041.464
unthenify96,359.91682,291.67982,507.05583,324.58438,842.74196,432.33297,113.0599,892.099
when.bindCallback822.083837.698848.358851.348789.546854.184844.102851.644
<!-- GitHub sanitizes my attempts to make this toggleable. Hide for now. bluebird#nodeify with bluebird x 1,889,496 ops/sec ±1.37% (83 runs sampled) bluebird#nodeify with npo: TypeError: this._then is not a function bluebird#nodeify with pinkie: TypeError: this._then is not a function bluebird#nodeify with q: TypeError: this._then is not a function bluebird#nodeify with rsvp: TypeError: this._then is not a function bluebird#nodeify with then: TypeError: this._then is not a function bluebird#nodeify with when: TypeError: this._then is not a function bluebird#nodeify with native: TypeError: this._then is not a function es-nodeify with bluebird x 1,247,981 ops/sec ±1.41% (78 runs sampled) es-nodeify with npo x 455,338 ops/sec ±3.80% (85 runs sampled) es-nodeify with pinkie x 466,965 ops/sec ±2.77% (81 runs sampled) es-nodeify with q x 64,703 ops/sec ±3.54% (73 runs sampled) es-nodeify with rsvp x 2,182,281 ops/sec ±2.07% (83 runs sampled) es-nodeify with then x 2,062,330 ops/sec ±0.67% (83 runs sampled) es-nodeify with when x 1,889,185 ops/sec ±0.62% (85 runs sampled) es-nodeify with native x 520,350 ops/sec ±0.39% (86 runs sampled) nodeify with bluebird x 147,455 ops/sec ±2.51% (74 runs sampled) nodeify with npo x 326,959 ops/sec ±2.44% (81 runs sampled) nodeify with pinkie x 325,972 ops/sec ±2.19% (82 runs sampled) nodeify with q x 53,878 ops/sec ±4.86% (66 runs sampled) nodeify with rsvp x 1,232,726 ops/sec ±4.10% (81 runs sampled) nodeify with then x 952,338 ops/sec ±0.98% (83 runs sampled) nodeify with when x 926,627 ops/sec ±5.41% (69 runs sampled) nodeify with native x 325,956 ops/sec ±3.21% (77 runs sampled) promiseNodeify with bluebird x 1,170,757 ops/sec ±2.39% (84 runs sampled) promiseNodeify with npo x 478,344 ops/sec ±1.87% (84 runs sampled) promiseNodeify with pinkie x 489,024 ops/sec ±0.47% (86 runs sampled) promiseNodeify with q x 62,906 ops/sec ±4.73% (69 runs sampled) promiseNodeify with rsvp x 2,097,277 ops/sec ±2.18% (84 runs sampled) promiseNodeify with then x 1,928,683 ops/sec ±0.99% (85 runs sampled) promiseNodeify with when x 1,497,451 ops/sec ±0.47% (83 runs sampled) promiseNodeify with native x 465,186 ops/sec ±3.41% (85 runs sampled) then#nodeify with bluebird x 131,589 ops/sec ±2.36% (81 runs sampled) then#nodeify with npo x 246,557 ops/sec ±1.59% (88 runs sampled) then#nodeify with pinkie x 245,428 ops/sec ±2.68% (82 runs sampled) then#nodeify with q x 49,655 ops/sec ±4.59% (70 runs sampled) then#nodeify with rsvp x 684,233 ops/sec ±0.56% (86 runs sampled) then#nodeify with then x 1,178,176 ops/sec ±0.77% (83 runs sampled) then#nodeify with when x 634,041 ops/sec ±2.52% (83 runs sampled) then#nodeify with native x 241,627 ops/sec ±2.28% (86 runs sampled) unthenify with bluebird x 96,360 ops/sec ±0.54% (85 runs sampled) unthenify with npo x 82,507 ops/sec ±1.38% (85 runs sampled) unthenify with pinkie x 83,325 ops/sec ±0.33% (85 runs sampled) unthenify with q x 38,843 ops/sec ±3.26% (75 runs sampled) unthenify with rsvp x 96,432 ops/sec ±4.63% (80 runs sampled) unthenify with then x 97,113 ops/sec ±2.47% (83 runs sampled) unthenify with when x 99,892 ops/sec ±0.48% (85 runs sampled) unthenify with native x 82,292 ops/sec ±0.39% (85 runs sampled) when with bluebird x 822 ops/sec ±1.11% (78 runs sampled) when with npo x 848 ops/sec ±0.90% (77 runs sampled) when with pinkie x 851 ops/sec ±0.99% (74 runs sampled) when with q x 790 ops/sec ±1.72% (73 runs sampled) when with rsvp x 854 ops/sec ±0.98% (75 runs sampled) when with then x 844 ops/sec ±0.94% (74 runs sampled) when with when x 852 ops/sec ±0.93% (75 runs sampled) when with native x 838 ops/sec ±0.94% (76 runs sampled) -->

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 appreciated. Contributors agree to abide by the Contributor Covenant Code of Conduct. If this is your first time contributing to a Free and Open Source Software project, consider reading How to Contribute to Open Source in the Open Source Guides.

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 project is available under the terms of the MIT License. See the summary at TLDRLegal.