promise-nodeify

Call a Node-style callback with the resolution value or rejection cause of a Promise without the common pitfalls.


Keywords
callback, nodeify, promise, nodejs, promise-nodeify
License
MIT
Install
npm install promise-nodeify@3.0.1

Documentation

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:

  • 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 an Error 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 causes unhandledRejection 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 unhandledRejections 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 Promise2 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 .cause3 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:

  1. Un-thenify serves a similar purpose, but wraps the Promise-returning function rather than taking the Promise as an argument.
  2. Temporarily reverted in petkaantonov/bluebird#151 and restored in petkaantonov/bluebird#168
  3. In response to petkaantonov/bluebird#434
  4. In response to petkaantonov/bluebird#170
  5. 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:

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 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.