├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── index.js ├── license ├── optimization-test.js ├── package.json ├── readme.md └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '0.12' 5 | - '0.10' 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var processFn = function (fn, P, opts) { 4 | return function () { 5 | var that = this; 6 | var args = new Array(arguments.length); 7 | 8 | for (var i = 0; i < arguments.length; i++) { 9 | args[i] = arguments[i]; 10 | } 11 | 12 | return new P(function (resolve) { 13 | args.push(function (result) { 14 | if (opts.multiArgs) { 15 | var results = new Array(arguments.length); 16 | 17 | for (var i = 0; i < arguments.length; i++) { 18 | results[i] = arguments[i]; 19 | } 20 | 21 | resolve(results); 22 | } else { 23 | resolve(result); 24 | } 25 | }); 26 | 27 | fn.apply(that, args); 28 | }); 29 | }; 30 | }; 31 | 32 | var pify = module.exports = function (obj, P, opts) { 33 | if (typeof P !== 'function') { 34 | opts = P; 35 | P = Promise; 36 | } 37 | 38 | opts = opts || {}; 39 | opts.exclude = opts.exclude || [/.+Sync$/]; 40 | 41 | var filter = function (key) { 42 | var match = function (pattern) { 43 | return typeof pattern === 'string' ? key === pattern : pattern.test(key); 44 | }; 45 | 46 | return opts.include ? opts.include.some(match) : !opts.exclude.some(match); 47 | }; 48 | 49 | var ret = typeof obj === 'function' ? function () { 50 | if (opts.excludeMain) { 51 | return obj.apply(this, arguments); 52 | } 53 | 54 | return processFn(obj, P, opts).apply(this, arguments); 55 | } : {}; 56 | 57 | return Object.keys(obj).reduce(function (ret, key) { 58 | var x = obj[key]; 59 | 60 | ret[key] = typeof x === 'function' && filter(key) ? processFn(x, P, opts) : x; 61 | 62 | return ret; 63 | }, ret); 64 | }; 65 | 66 | pify.all = pify; 67 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Sam Verschueren (github.com/SamVerschueren) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /optimization-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-fallthrough */ 2 | 'use strict'; 3 | var assert = require('assert'); 4 | var Promise = require('pinkie-promise'); 5 | var v8 = require('v8-natives'); 6 | var fn = require('./'); 7 | 8 | function assertOptimized(fn, name) { 9 | var status = v8.getOptimizationStatus(fn); 10 | 11 | switch (status) { 12 | case 1: 13 | // fn is optimized 14 | return; 15 | case 2: 16 | assert(false, name + ' is not optimized (' + status + ')'); 17 | case 3: 18 | // fn is always optimized 19 | return; 20 | case 4: 21 | assert(false, name + ' is never optimized (' + status + ')'); 22 | case 6: 23 | assert(false, name + ' is maybe deoptimized (' + status + ')'); 24 | default: 25 | assert(false, 'unknown OptimizationStatus: ' + status + ' (' + name + ')'); 26 | } 27 | } 28 | 29 | var sut = fn({ 30 | unicorn: function (cb) { 31 | cb('unicorn'); 32 | } 33 | }, Promise); 34 | 35 | sut.unicorn().then(function () { 36 | v8.optimizeFunctionOnNextCall(sut.unicorn); 37 | 38 | return sut.unicorn().then(function () { 39 | assertOptimized(sut.unicorn, 'unicorn'); 40 | }); 41 | }).catch(function (err) { 42 | console.log(err.stack); 43 | process.exit(1); 44 | }); 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfpify", 3 | "version": "1.0.1", 4 | "description": "Promisify a result-first callback-style function.", 5 | "license": "MIT", 6 | "repository": "SamVerschueren/rfpify", 7 | "author": { 8 | "name": "Sam Verschueren", 9 | "email": "sam.verschueren@gmail.com", 10 | "url": "github.com/SamVerschueren" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava && npm run optimization-test", 17 | "optimization-test": "node --allow-natives-syntax optimization-test.js" 18 | }, 19 | "files": [ 20 | "index.js" 21 | ], 22 | "keywords": [ 23 | "promise", 24 | "promises", 25 | "promisify", 26 | "callback", 27 | "then", 28 | "async" 29 | ], 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "ava": "*", 33 | "pinkie-promise": "^1.0.0", 34 | "v8-natives": "0.0.2", 35 | "xo": "*" 36 | }, 37 | "xo": { 38 | "ignores": [ 39 | "test.js" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # rfpify 2 | 3 | > Promisify a result-first callback-style function. 4 | 5 | --- 6 | 7 |

Deprecated in favour of pify with the errorFirst option.

8 | 9 | --- 10 | 11 | 12 | ## Install 13 | 14 | ``` 15 | $ npm install --save rfpify 16 | ``` 17 | 18 | 19 | ## Usage 20 | 21 | ```js 22 | const rfpify = require('rfpify'); 23 | 24 | rfpify(stream.once.bind(stream))('data').then(data => { 25 | // handle data 26 | }); 27 | ``` 28 | 29 | 30 | ## API 31 | 32 | ### rfpify(input, [promiseModule], [options]) 33 | 34 | Returns a promise wrapped version of the supplied function or module. 35 | 36 | #### input 37 | 38 | Type: `function`, `object` 39 | 40 | Result-first callback-style function. 41 | 42 | #### promiseModule 43 | 44 | Type: `function` 45 | 46 | Custom promise module to use instead of the native one. 47 | 48 | Check out [`pinkie-promise`](https://github.com/floatdrop/pinkie-promise) if you need a tiny promise polyfill. 49 | 50 | #### options 51 | 52 | ##### multiArgs 53 | 54 | Type: `boolean` 55 | Default: `false` 56 | 57 | By default, the promisified function will only return the first argument from the callback, which works fine for most APIs. Turning this on will make it return an array of 58 | all arguments from the callback, instead of just the first argument. 59 | 60 | ##### include 61 | 62 | Type: `array` of (`string`|`regex`) 63 | 64 | Methods in a module to promisify. Remaining methods will be left untouched. 65 | 66 | ##### exclude 67 | 68 | Type: `array` 69 | Default: `[/.+Sync$/]` 70 | 71 | Methods in a module **not** to promisify. Methods with names ending with 'Sync' are excluded by default. 72 | 73 | ##### excludeMain 74 | 75 | Type: `boolean` 76 | Default: `false` 77 | 78 | By default, if given module is a function itself, this function will be promisified. Turn this option on if you want to promisify only methods of the module. 79 | 80 | ```js 81 | const rfpify = require('rfpify'); 82 | 83 | function fn() { 84 | return true; 85 | } 86 | 87 | fn.method = (data, callback) => { 88 | setImmediate(() => { 89 | callback(data); 90 | }); 91 | }; 92 | 93 | // promisify methods but not fn() 94 | const promiseFn = rfpify.all(fn, {excludeMain: true}); 95 | 96 | if (promiseFn()) { 97 | promiseFn.method('hi').then(data => { 98 | console.log(data); 99 | }); 100 | } 101 | ``` 102 | 103 | ## Related 104 | 105 | - [pify](https://github.com/sindresorhus/pify) - Promisify a callback-style function 106 | 107 | ## License 108 | 109 | MIT © [Sam Verschueren](http://github.com/SamVerschueren) 110 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import test from 'ava'; 3 | import Promise from 'pinkie-promise'; 4 | import fn from './'; 5 | 6 | function fixture(cb) { 7 | setImmediate(() => cb('unicorn')); 8 | } 9 | 10 | function fixture2(x, cb) { 11 | setImmediate(() => cb(x)); 12 | } 13 | 14 | function fixture3(cb) { 15 | setImmediate(() => cb('unicorn', 'rainbow')); 16 | } 17 | 18 | function fixture4() { 19 | return 'unicorn'; 20 | } 21 | 22 | function fixture5(cb) { 23 | setImmediate(() => cb('unicorn')); 24 | return true; 25 | } 26 | 27 | fixture5.meow = cb => { 28 | setImmediate(() => cb('unicorn')); 29 | }; 30 | 31 | test('main', async t => { 32 | t.is(typeof fn(fixture)().then, 'function'); 33 | 34 | t.is(await fn(fixture)(), 'unicorn'); 35 | }); 36 | 37 | test('pass argument', async t => { 38 | t.is(await fn(fixture2)('rainbow'), 'rainbow'); 39 | }); 40 | 41 | test('custom Promise module', async t => { 42 | t.is(await fn(fixture, Promise)(), 'unicorn'); 43 | }); 44 | 45 | test('multiArgs option', async t => { 46 | t.same(await fn(fixture3, {multiArgs: true})(), ['unicorn', 'rainbow']); 47 | }); 48 | 49 | test('wrap core method', async t => { 50 | const result = await fn(fs.readFile, {multiArgs: true})('package.json'); 51 | 52 | t.is(result[0], null); 53 | t.is(JSON.parse(result[1]).name, 'rfpify'); 54 | }); 55 | 56 | test('module support', async t => { 57 | const result = await fn.all(fs, {multiArgs: true}).readFile('package.json'); 58 | 59 | t.is(result[0], null); 60 | t.is(JSON.parse(result[1]).name, 'rfpify'); 61 | }); 62 | 63 | test('module support - preserves non-function members', t => { 64 | const module = { 65 | method: function () {}, 66 | nonMethod: 3 67 | }; 68 | 69 | t.same(Object.keys(module), Object.keys(fn.all(module))); 70 | t.end(); 71 | }); 72 | 73 | test('module support - transforms only members in opions.include', t => { 74 | const module = { 75 | method1: fixture, 76 | method2: fixture2, 77 | method3: fixture4 78 | }; 79 | 80 | const pModule = fn.all(module, { 81 | include: ['method1', 'method2'] 82 | }); 83 | 84 | t.is(typeof pModule.method1().then, 'function'); 85 | t.is(typeof pModule.method2('fainbow').then, 'function'); 86 | t.not(typeof pModule.method3().then, 'function'); 87 | t.end(); 88 | }); 89 | 90 | test('module support - doesn\'t transform members in opions.exclude', t => { 91 | const module = { 92 | method1: fixture4, 93 | method2: fixture4, 94 | method3: fixture 95 | }; 96 | 97 | const pModule = fn.all(module, { 98 | exclude: ['method1', 'method2'] 99 | }); 100 | 101 | t.not(typeof pModule.method1().then, 'function'); 102 | t.not(typeof pModule.method2().then, 'function'); 103 | t.is(typeof pModule.method3().then, 'function'); 104 | t.end(); 105 | }); 106 | 107 | test('module support - options.include over opions.exclude', t => { 108 | const module = { 109 | method1: fixture, 110 | method2: fixture2, 111 | method3: fixture4 112 | }; 113 | 114 | const pModule = fn.all(module, { 115 | include: ['method1', 'method2'], 116 | exclude: ['method2', 'method3'] 117 | }); 118 | 119 | t.is(typeof pModule.method1().then, 'function'); 120 | t.is(typeof pModule.method2('rainbow').then, 'function'); 121 | t.not(typeof pModule.method3().then, 'function'); 122 | t.end(); 123 | }); 124 | 125 | test('module support — function modules', t => { 126 | const pModule = fn.all(fixture5); 127 | 128 | t.is(typeof pModule().then, 'function'); 129 | t.is(typeof pModule.meow().then, 'function'); 130 | t.end(); 131 | }); 132 | 133 | test('module support — function modules exclusion', t => { 134 | const pModule = fn.all(fixture5, { 135 | excludeMain: true 136 | }); 137 | 138 | t.is(typeof pModule.meow().then, 'function'); 139 | t.not(typeof pModule(function () {}).then, 'function'); 140 | t.end(); 141 | }); 142 | --------------------------------------------------------------------------------