UNPKG

promise-breaker

Version:

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

240 lines (177 loc) 9.53 kB
![Build Status](https://github.com/jwalton/node-promise-breaker/workflows/GitHub%20CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/jwalton/node-promise-breaker/badge.svg)](https://coveralls.io/r/jwalton/node-promise-breaker) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/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: ```js 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: ```js 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: ```js // 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: ```js 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: ```js 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: ```js 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: ```js 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()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call), 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()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/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: ```js 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.