包详细信息

promise-breaker

jwalton4.3mMIT6.0.0

Library to help write libraries that accept both promises and callbacks.

promise, callback, library

自述文件

Build Status Coverage Status semantic-release

What is it?

promise-breaker makes it easy to write functions that will accept an optional callback, or return a Promise if a callback is not provided. You can use callbacks or Promises in your implementation, and callers can call with either a callback or expect a Promise. It's a library that makes it easy to write libraries for others.

Installation

npm install --save promise-breaker

Requirements

This library assumes that Promise is a defined global variable. If this is not the case on your platform, you can use a polyfill:

npm install --save es6-promise

Then somewhere in your node.js application:

if(!global.Promise) {
    global.Promise = require('es6-promise').Promise;
}

Or in your client-side app:

if(!window.Promise) {
    window.Promise = require('es6-promise').Promise;
}

If you don't want to set the global, you can pass an optional Promise implementation to promise-breaker:

var MyPromise = require('es6-promise').Promise;
promiseBreaker = require('promise-breaker').withPromise(MyPromise);

Summary

With the growing popularity of Promises these days, if you're a library author, it's nice to be able to provide your clients with a library that will take an optional callback, and if the callback isn't provided, return a Promise. If you've ever tried to do this, you know that there's a lot of finicky boilerplate involved in every function you write. Providing callback support is also pretty important if you prefer to write your library using Promises internally.

'promise-breaker' makes this really easy. If you prefer writing in callback style:

export function myFunc(done=null) {
    return pb.addPromise(done, done => // Add this wrapper around your async function
        doThing((err, thing) => {
            if(err) {return done(err);}
            doOtherThing(thing, (err, otherThing) => {
                if(err) {return done(err);}
                done(null, otherThing);
            });
        });
    );
}

or if you prefer Promise style:

export function myFunc(done=null) {
    return pb.addCallback(done, // Add this wrapper around your returned Promise.
        doThing()
        .then(result => doOtherThing(result))
    );
}

If you're using arrow functions or using commonjs exports, it's even easier to use promise-breaker to create functions that generate a Promise or accept a callback:

// Both of these will take an optional `done`, and if not provided return a Promise.
exports.myPromiseFunc = pb.break({args: 0}, () => {
    return Promise.resolve("Hello World");
});

exports.myCbFunc = pb.make({args: 1}, done => {
    done(null, "Hello World");
});

The names make() and break() here come from the idea that you are making a callback into a promise, or breaking a promise down into a callback. Note that make() and break() rely on the .length of the function you pass in. In ES6, default parameters do not count towards the length of the function, so you need to explicitly tell promise-breaker how many parameters are expected in the args parameter. If you're not using default arguments, you can omit the options parameter altogether, but this is a bad habit, as promise-breaker unfortunately has no way to detect if you get it wrong.

The other thing you often want to do when writing a library is call into a function without knowing whether it returns a promise or expects a callback. Again, promise-breaker makes this easy:

export function doStuff(fn) {
    // This works just like `fn.call` except it will add a `done` if `fn.length` is bigger than the parameter count.
    // So here, this will either call `fn("hello world")` and get back a Promise or `fn("hello world", done)` and
    // convert the callback into a Promise for you.
    pb.call(fn, null, "hello world")
    .catch(err => console.log(err));
}

Or, in callback style:

export function doStuff(fn) {
    pb.callWithCb(fn, null, "hello world", err => {
        if(err) return console.log(err);
    });
}

API

pb.make([options,] fn)

  • options.args - In ES6, default parameters do not count towards a functions .length. If your fn uses default parameters, you must specify the total parameter count in args. E.g.: const myFn = pb.make({args: 2}, (x, y=null) => ...); If you do not specify args, then promise-breaker will use fn.length instead.

make() takes a function which accepts a callback(err, result) as its last parameter, and returns a new function which accepts an optional callback as its last parameter. If a callback is provided, this new function will behave exactly like the original function. If the callback is not provided, then the new function will return a Promise.

Since Promises only allow a single value to be returned, if fn passes more than two arguments to callback(...), then (as of v3.0.0) any arguments after the error will be transformed into an array and returned via the Promise as a single combined argument. This does not affect the case where the transformed function is called with a callback.

For example:

var myFunc = pb.make(function(callback) {
    // We're returning multiple values via callback
    callback(null, "a", "b");
})

// Callback style
myFunc(function(err, a, b) {...});

// Promise style
myFunc()
.then(function(results) {
    // Promises only let us return a single value, so we return an array.
    var a = results[0];
    var b = results[1];
    ...
})
.catch(function(err) {...});

pb.break([options,] fn)

  • options.args - In ES6, default parameters do not count towards a functions .length. If your fn uses default parameters, you must specify the total parameter count in args. E.g.: const myFn = pb.break({args: 3}, (x, y=null, done=null) => ...); If you do not specify args, then promise-breaker will use fn.length instead.

break(fn) is the opposite of make(fn). fn here is a function which returns a Promise. break(fn) will generate a new function with an extra parameter, an optional callback(err, result). If no callback is provided, the generated function will behave exactly like the original function. If a callback is provided, then the generated function will return null, and will pass any results that would have been returned via the Promise via the callback instead.

addPromise(done, fn)

Used to add Promise support to a callback-based function.

Calls fn(cb). If done is provided, it is passed directly as cb and addPromise returns undefined. If done is not provided, addPromise will generate an appropriate callback and return a Promise. If fn is called with more than two arguments (with multiple results, in other words) then the Promise will resolve to an array of results.

Use it like this:

export function addAsync(x, y, done=null) {
    return pb.addPromise(done, done => done(null, x + y));
}

addCallback(done, promise)

Used to add callback support to a promise-based function.

If done is not provided, returns the promise passed in. If done is provided, this will wait for promise to resolve or reject and then call done(err, result) appropriately. Note that promise can also be a function that takes no arguments and returns a Promise.

Use it like this:

export function addAsync(x, y, done=null) {
    return pb.addCallback(done, Promise.resolve(x + y));
}

pb.apply(fn, thisArg, args[, cb])

Much like Function.prototype.apply(), this calls a function, but this lets you call into a function when you don't know whether the function is expecting a callback or is going to return a Promise. fn is the function you wish to call. Under the hood, if fn.length is equal to args.length, this will call fn with the parameters provided, and then return the Promise (or wrap a returned value in a Promise). If fn.length is args.length + 1, then a callback will be added.

If cb is provided, apply will call into cb with a result, otherwise apply will itself return a Promise.

pb.call(fn, thisArg[, arg1[, arg2[, ...]]))

This is the Function.prototype.call() equivalent of apply(). Note that this always returns a Promise. If you need a callback, use callWithCb() instead.

Note that this is handy shortcut for promisifying a callback-based API:

pb.call(done => fs.readFile(filename, {encoding: 'utf8'}, done))
.then(fileContents => ...);

pb.callWithCb(fn, argumentCount, thisArg[, arg1[, arg2[, ...[, cb]]]])

Similar to pb.call(), but instead of returning a Promise this will call the provided callback.

pb.withPromise(promiseImpl)

Returns a new {make, break, addPromise, addCallback, apply, call, callWithCb} object which uses the specified promiseImpl constructor to create new Promises.

更新日志

6.0.0 (2022-08-11)

Bug Fixes

  • Update typescript types to use modules instead of export=. (a9cdb29)

BREAKING CHANGES

  • For 99% of users, this should have no effect, but I'll bump the major version just to be safe.

5.0.0 (2019-06-25)

  • feat(Drop support for node v6 and node v8.): (187b75d)

BREAKING CHANGES

  • Drop support for node v6 and node v8.

4.1.13 (2018-11-29)

Bug Fixes

  • Fix typescript def for addPromise. (fa4fb38)

4.1.12 (2018-11-29)

Bug Fixes

  • Better typescript definitions. (604b106)

4.1.11 (2018-05-07)

Bug Fixes

  • typescript: Add generic to addPromise(). (de41bf2)

v4.1.10

  • Add typescript support.

v4.1.4

  • Remove accidental ES6. :)

v4.1.3

  • Support async/await functions.

v4.1.2

  • Relaxed rules for apply(), call(), and friends. These used to require a function that took exactly n arguments or n+1 arguments (the Promise and the callback cases, respectively). These functions now also accept functions with fewer parameters that return a Promise (possibly the function you're calling into doesn't care about some of the parameters you are passing).

v4.1.1

  • Correctly handle exceptions from callbacks - convert into uncaught exceptions instead of unhandled rejections.

v4.1.0

  • Added callWithCb(), addPromise(), and addCallback().
  • Added args parameter to make() and break().
  • Remove node v4 and v5 from the travis tests, so we can write tests in ES6 syntax.
  • Convert tests to javascript, remove dev dependency on coffee-script.

v4.0.3

Roll back v4.0.2. If someone passes in a strange function that relies on arguments, then we'd throw an error fail in v4.0.0, but we do completely the wrong thing in v4.0.2.

v4.0.2

Relax error conditions for callFn() and applyFn(). If a function is passed that takes too few parameters, we assume it is Promise based.

v4.0.0

  • Add call() and apply().
  • Breaking Change - Previously, when using pb.callFn() or pb.applyFn(), if the called function returned a thenable, we would assume the function was Promise based, and would otherwise assume it was callback based. This leads to a dangerous problem if you call a function that returns a scalar, though:

      let fn = (str) => "hello " + str;
      pb.callFn(fn, 1, null, "world")
      .then(result => expect(result).to.equal("hello world"));
    

    Here, fn.length is 1, and we're telling callFn we want to call an async function that takes 1 parameter, not including the callback. If fn doesn't return a Promise here, the expected behavior would be to wrap the return value in a Promise and return it. In v3.0.0, we would have assumed that since fn did not return a Promise, it must be callback based, so we would wait for the callback forever. This is dangerous behavior.

    In v4.0.0, we now decide whether to treat fn as a Promise function or a callback function based on fn.length. If you call callFn or applyFn, and tell it you are expecting a function that takes 1 parameter, promise-breaker will now throw an error if fn does not take 1 or 2 parameters.

v3.0.0

  • Breaking Change - When using pb.make(fn), if fn passes more than two arguments to its callback, then these arguments will be transformed into an array and passed to the Promise. Previous versions would only ever return the first value passed to the callback.