diff --git a/README.md b/README.md index 21315f7..3afce3e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Promisify a callback-based function using [`any-promise`](https://github.com/kev - Preserves function names - Uses a native promise implementation if available and tries to fall back to a promise implementation such as `bluebird` -- Converts multiple arguments from the callback into an `Array` +- Converts multiple arguments from the callback into an `Array`, also support change the behavior by `options.multiArgs` - Resulting function never deoptimizes - Supports both callback and promise style @@ -20,6 +20,20 @@ An added benefit is that `throw`n errors in that async function will be caught b ## API +### fn = thenify(fn, options) + +Promisifies a function. + +### Options + +`options` are optional. + +- `options.withCallback` - support both callback and promise style, default to `false`. +- `options.multiArgs` - change the behavior when callback have multiple arguments. default to `true`. + - `true` - converts multiple arguments to an array + - `false`- always use the first argument + - `Array` - converts multiple arguments to an object with keys provided in `options.multiArgs` + - Turn async functions into promises ```js @@ -32,6 +46,19 @@ var somethingAsync = thenify(function somethingAsync(a, b, c, callback) { - Backward compatible with callback +```js +var thenify = require('thenify'); + +var somethingAsync = thenify(function somethingAsync(a, b, c, callback) { + callback(null, a, b, c); +}, { withCallback: true }); + +// somethingAsync(a, b, c).then(onFulfilled).catch(onRejected); +// somethingAsync(a, b, c, function () {}); +``` + +or use `thenify.withCallback()` + ```js var thenify = require('thenify').withCallback; @@ -43,9 +70,37 @@ var somethingAsync = thenify(function somethingAsync(a, b, c, callback) { // somethingAsync(a, b, c, function () {}); ``` -### var fn = thenify(fn) +- Always return the first argument in callback -Promisifies a function. +```js +var thenify = require('thenify'); + +var promise = thenify(function (callback) { + callback(null, 1, 2, 3); +}, { multiArgs: false }); + +// promise().then(function onFulfilled(value) { +// assert.equal(value, 1); +// }); +``` + +- Converts callback arguments to an object + +```js +var thenify = require('thenify'); + +var promise = thenify(function (callback) { + callback(null, 1, 2, 3); +}, { multiArgs: [ 'one', 'tow', 'three' ] }); + +// promise().then(function onFulfilled(value) { +// assert.deepEqual(value, { +// one: 1, +// tow: 2, +// three: 3 +// }); +// }); +``` [gitter-image]: https://badges.gitter.im/thenables/thenify.png [gitter-url]: https://gitter.im/thenables/thenify diff --git a/index.js b/index.js index b714a38..161d32e 100644 --- a/index.js +++ b/index.js @@ -12,9 +12,9 @@ module.exports = thenify * @api public */ -function thenify($$__fn__$$) { +function thenify($$__fn__$$, options) { assert(typeof $$__fn__$$ === 'function') - return eval(createWrapper($$__fn__$$.name)) + return eval(createWrapper($$__fn__$$.name, options)) } /** @@ -25,25 +25,41 @@ function thenify($$__fn__$$) { * @api public */ -thenify.withCallback = function ($$__fn__$$) { +thenify.withCallback = function ($$__fn__$$, options) { assert(typeof $$__fn__$$ === 'function') - return eval(createWrapper($$__fn__$$.name, true)) + options = options || {} + options.withCallback = true + if (options.multiArgs === undefined) options.multiArgs = true + return eval(createWrapper($$__fn__$$.name, options)) } -function createCallback(resolve, reject) { +function createCallback(resolve, reject, multiArgs) { return function(err, value) { if (err) return reject(err) var length = arguments.length - if (length <= 2) return resolve(value) + + if (length <= 2 || !multiArgs) return resolve(value) + + if (Array.isArray(multiArgs)) { + var values = {} + for (var i = 1; i < length; i++) values[multiArgs[i - 1]] = arguments[i] + return resolve(values) + } + var values = new Array(length - 1) for (var i = 1; i < length; ++i) values[i - 1] = arguments[i] resolve(values) } } -function createWrapper(name, withCallback) { +function createWrapper(name, options) { name = (name || '').replace(/\s|bound(?!$)/g, '') - withCallback = withCallback ? + options = options || {} + // default to true + var multiArgs = options.multiArgs !== undefined ? options.multiArgs : true + multiArgs = 'var multiArgs = ' + JSON.stringify(multiArgs) + '\n' + + var withCallback = options.withCallback ? 'var lastType = typeof arguments[len - 1]\n' + 'if (lastType === "function") return $$__fn__$$.apply(self, arguments)\n' : '' @@ -51,12 +67,13 @@ function createWrapper(name, withCallback) { return '(function ' + name + '() {\n' + 'var self = this\n' + 'var len = arguments.length\n' + + multiArgs + withCallback + 'var args = new Array(len + 1)\n' + 'for (var i = 0; i < len; ++i) args[i] = arguments[i]\n' + 'var lastIndex = i\n' + 'return new Promise(function (resolve, reject) {\n' - + 'args[lastIndex] = createCallback(resolve, reject)\n' + + 'args[lastIndex] = createCallback(resolve, reject, multiArgs)\n' + '$$__fn__$$.apply(self, args)\n' + '})\n' + '})' diff --git a/test/options.js b/test/options.js new file mode 100644 index 0000000..b30d555 --- /dev/null +++ b/test/options.js @@ -0,0 +1,57 @@ + +var assert = require('assert') + +var promisify = require('..') + +var setImmediate = global.setImmediate || function (fn) { + process.nextTick(fn) +} + +describe('options', function () { + describe('.withCallback', function () { + function fn(cb) { + cb(null, true) + } + + it('promise', function () { + return promisify(fn, { withCallback: true })().then(function (val) { + assert.equal(val, true) + }) + }) + + it('callback', function (done) { + return promisify(fn, { withCallback: true })(function (err, val) { + assert.equal(val, true) + done() + }) + }) + }) + + describe('.multiArgs', function () { + function fn(cb) { + cb(null, 1, 2, 3) + } + + it('default to true', function () { + return promisify(fn)().then(function (values) { + assert.deepEqual(values, [1, 2, 3]) + }) + }) + + it('set to false', function () { + return promisify(fn, { multiArgs: false })().then(function (value) { + assert.equal(value, 1) + }) + }) + + it('set to array', function () { + return promisify(fn, { multiArgs: [ 'one', 'tow', 'three' ] })().then(function (value) { + assert.deepEqual(value, { + one: 1, + tow: 2, + three: 3 + }) + }) + }) + }) +})