Git Product home page Git Product logo

pify's Introduction

pify

Promisify a callback-style function

Install

npm install pify

Usage

import fs from 'fs';
import pify from 'pify';

// Promisify a single function.
const data = await pify(fs.readFile)('package.json', 'utf8');
console.log(JSON.parse(data).name);
//=> 'pify'

// Promisify all methods in a module.
const data2 = await pify(fs).readFile('package.json', 'utf8');
console.log(JSON.parse(data2).name);
//=> 'pify'

API

pify(input, options?)

Returns a Promise wrapped version of the supplied function or module.

input

Type: Function | object

Callback-style function or module whose methods you want to promisify.

options

Type: object

multiArgs

Type: boolean
Default: false

By default, the promisified function will only return the second argument from the callback, which works fine for most APIs. This option can be useful for modules like request that return multiple arguments. Turning this on will make it return an array of all arguments from the callback, excluding the error argument, instead of just the second argument. This also applies to rejections, where it returns an array of all the callback arguments, including the error.

import request from 'request';
import pify from 'pify';

const pRequest = pify(request, {multiArgs: true});

const [httpResponse, body] = await pRequest('https://sindresorhus.com');
include

Type: Array<string | RegExp>

Methods in a module to promisify. Remaining methods will be left untouched.

exclude

Type: Array<string | RegExp>
Default: [/.+(?:Sync|Stream)$/]

Methods in a module not to promisify. Methods with names ending with 'Sync' are excluded by default.

excludeMain

Type: boolean
Default: false

If the given module is a function itself, it will be promisified. Enable this option if you want to promisify only methods of the module.

import pify from 'pify';

function fn() {
	return true;
}

fn.method = (data, callback) => {
	setImmediate(() => {
		callback(null, data);
	});
};

// Promisify methods but not `fn()`.
const promiseFn = pify(fn, {excludeMain: true});

if (promiseFn()) {
	console.log(await promiseFn.method('hi'));
}
errorFirst

Type: boolean
Default: true

Whether the callback has an error as the first argument. You'll want to set this to false if you're dealing with an API that doesn't have an error as the first argument, like fs.exists(), some browser APIs, Chrome Extension APIs, etc.

promiseModule

Type: Function

Custom promise module to use instead of the native one.

FAQ

How is this different from Node.js's util.promisify?

  • Pify existed long before util.promisify.
  • Pify is faster.
  • Pify supports wrapping a whole module/object, not just a specific method.
  • Pify has useful options like the ability to handle multiple arguments (multiArgs).
  • Pify does not have magic behavior for certain Node.js methods and instead focuses on predictability.

How can I promisify a single class method?

Class methods are not bound, so when they're not called on the class itself, they don't have any context. You can either promisify the whole class or use .bind().

import pify from 'pify';
import SomeClass from './some-class.js';

const someInstance = new SomeClass();

// ❌ `someFunction` can't access its class context.
const someFunction = pify(someClass.someFunction);

// ✅ The whole class is promisified and the `someFunction` method is called on its class.
const someClassPromisified = pify(someClass);
someClassPromisified.someFunction();

// ✅ `someFunction` is bound to its class before being promisified.
const someFunction = pify(someClass.someFunction.bind(someClass));

Why is pify choosing the last function overload when using it with TypeScript?

If you're using TypeScript and your input has function overloads, then only the last overload will be chosen and promisified. This is a TypeScript limitation.

If you need to choose a different overload, consider using a type assertion:

function overloadedFunction(input: number, callback: (error: unknown, data: number) => void): void
function overloadedFunction(input: string, callback: (error: unknown, data: string) => void): void {
	/* … */
}

const fn = pify(overloadedFunction as (input: number, callback: (error: unknown, data: number) => void) => void)
//    ^? (input: number) => Promise<number>

Related

  • p-event - Promisify an event by waiting for it to be emitted
  • p-map - Map over promises concurrently
  • More…

pify's People

Contributors

coreyfarrell avatar dansnow avatar danthegoodman avatar dsblv avatar frangio avatar fregante avatar kevinoid avatar linusu avatar mightyiam avatar mottie avatar richienb avatar samverschueren avatar schnittstabil avatar sindresorhus avatar stephenplusplus avatar tom-sherman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pify's Issues

Does not set default export

At the moment this library is kind of ugly to use with the ES6 import syntax.

The current required syntax is:

import * as pify from 'pify' 

Expected syntax

import pify from pify

Callback with single argument not error first

EDIT: Sorry for nonsense request - one of my weaker moments - you can close it.

Hello,
If function passed into pify has only one argument it's usually something like data, result, or so. This callback has only option for success, thus must be used with additional options - errorFirst: false.

Would it be possible to consider change of default behavior, since from #31 we have support for not error first callbacks?

When callback.length is 1, function takes single argument and it would be considered as errorFirst: false by default.
It would be less verbose.
If it would or wouldn't be less clear is on your consideration.

Thank you.

Methods for keeping this

I find that I quite often need to preserve this when calling functions that takes callbacks. I have used something similar to the code below, but I would like to hear what you think of the matter.

Is there an easy way to support this use case?

function promise (obj, fn) {
  return pify(obj[fn]).bind(obj)
}

await promise(linus, 'walk')(100, 'miles')
console.log('Linus walked 100 miles')

// vs.

linus.walk(100, 'miles', () => console.log('Linus walked 100 miles'))

Callback as first argument

Is it possible to pify following function?

doIt(callback, options);

I have a library where the callback is the first argument and the second one is options (usually in most API's it is inverted)

Error in line: for (const key in obj)

Hello, the line 78 that says:
for (const key in obj) {
... should be rewritten to...
for (var key in obj) {
... since key will change within the loop.
Otherwise some browsers like old versions of Firefox will throw the error: SyntaxError: missing = in const declaration

Support functions that don't pass an error to callback

I've tried to use pify with Inquirer.js, it works, but the behavior is not the expected one.

const inquirer = pify(require('inquirer'));

inquirer.prompt([ questions ]).then((answers) => {
  console.log('Then:', answers);
}).catch((error) => {
  console.log('Catch:', error);
});

The output is Catch: { Answers }, this is because inquirer doesn't call the callback function with an error as the first argument, it just passes the Answers object as far as I know.

I think that there are two ways to solve this, an option or check if the first argument is an instance of Error.

Do you want to support this case?

multiArgs for only some methods on a module/object

Currently if you pass { multiArgs: true }, all methods will resolve to an array. But, sometimes you might want to just have some of the methods resolve to the array while others resolve to a single value.

Here's a real world example:
In mysql a created pool has a getConnection method, and a query method. The callback for getConnection only has a single argument, while query has 2(results & fields).

The consumer can array destructure easy enough.

const [ connection ] = await pool.getConnection()

But it requires more work than what they probably expect.

const connection = await pool.getConnection()

If there's interest in dealing with this, I imagine multiArgs being able to take an array of strings(method names). And those methods would resolve to an array of values, and all other methods would return a single value, as is the default.

Thoughts?

Return ES2015 Proxy object

Issuehunt badges

ECMAScript6 Proxies are currently not available on node, therefore this issue is a reminder for future versions.

pify(obj) should return an ECMAScript6 Proxy object, forwarding almost all operations to obj, and promisifying only some functions, thus we almost surely get rid of most non-enumerable and inheritance issues.


IssueHunt Summary

frangio frangio has been rewarded.

Backers (Total: $70.00)

Submitted pull Requests


Tips


IssueHunt has been backed by the following sponsors. Become a sponsor

Add TypeScript type definition

Issuehunt badges

Would be nice to have TypeScript type definitions bundled in this package. The types should be strict and should be generic so it preserves the original function type.

The type definition should follow this styleguide, and should have full docs and type tests.

Some examples:

Note that this is not an easy task and requires you to have advanced TypeScript experience. There have been multiple attempts and a lot of wasted time on my part in the past. If you intend to work on this, please look at the previous attempts and the feedback given there.


IssueHunt Summary

tom-sherman tom-sherman has been rewarded.

Backers (Total: $100.00)

Submitted pull Requests


Tips

pify for gm doesn't seems to be working

Hi,

I'm trying to fix AVA tests for some functionality and I need to Promisify gm(...).size() function. Unfortunately I'm not able to do that properly:

const out = await pify(gm(image.data).size());
console.log("gm output: " + out.width);
---
  gm output: undefined
const out = await pify(gm(image.data).size)();
console.log("gm output: " + out.width);
---
  1 failed

  resize-jpeg › Resize JPEG with jpegoptim
  Error: Cannot read property 'size' of undefined
    proto.(anonymous function) (node_modules/gm/lib/getters.js:47:20)
    node_modules/pify/index.js:29:7
    node_modules/pify/index.js:12:10
    ret (node_modules/pify/index.js:56:34)
    Test.<anonymous> (test/resize-jpeg.js:37:20)
    Generator.next (<anonymous>)
const out = await pify(gm(image.data).size.bind(gm))();
console.log("gm output: " + out.width);
---
  1 failed

  resize-jpeg › Resize JPEG with jpegoptim
  Error: Cannot read property 'size' of undefined
    proto.(anonymous function) (node_modules/gm/lib/getters.js:47:20)
    node_modules/pify/index.js:29:7
    node_modules/pify/index.js:12:10
    ret (node_modules/pify/index.js:56:34)
    Test.<anonymous> (test/resize-jpeg.js:37:20)
    Generator.next (<anonymous>)
const out = await pify(gm(image.data)).size();
console.log("gm output: " + out.width);
---
  1 failed

  resize-jpeg › Resize JPEG with jpegoptim
  Error: pify(...).size is not a function
    Test.<anonymous> (test/resize-jpeg.js:37:43)
    Generator.next (<anonymous>)

So, my question is: if it's possible to pify gm and if so, I would like to know how?

not getting full parameters of callbacks

e.g.

const dns = require('dns');
const pify = require('pify');

const dnsLookup = pify(dns.lookup);

(async () => {
    const result = await dnsLookup('example.com');
    console.log(result); // => 93.184.216.34
})()

only getting ip address, from the doc, the callback is passing family param too

need return object, e.g.

  const {address, family} = await dnsLookup('example.com');

pify(setTimeout) does not work correctly in Node.js

This works with util.promisify():

const { promisify } = require('util')
const setTimeoutAsync = promisify(setTimeout)

async function init () {
  console.log('Waiting 5 seconds...')
  await setTimeoutAsync(5000)
  console.log('Done!')
}

init()

It would be nice if it worked with pify() too!

Not sure if this requires using util.promisify() when running in Node.js, as described in #41 or if it can just be implemented by reading the util.promisify.custom symbol on the function, as described in the Node.js docs: https://nodejs.org/api/util.html#util_custom_promisified_functions

Module should not trigger getters on promisified function

> require('pify')(require('webpack'))
// => (node:97281) [DEP_WEBPACK_JAVASCRIPT_MODULES_PLUGIN]
// => (node:97281) [DEP_WEBPACK_LIBRARY_TEMPLATE_PLUGIN]
// => (node:97281) [DEP_WEBPACK_SINGLE_ENTRY_PLUGIN]
// => (node:97281) [DEP_WEBPACK_OPTIONS_DEFAULTER

See here on how those getters are defined. Same with util.promisify works as expected.

the original `options` object is altered

The options object is altered. I think it's better to use object-assign here.

Didn't create a PR immediately because I don't know if you want a dependency for such a small improvement as pify is dependency-less at the moment.

Example of excludeMain option is not correct

fn.method = (data, callback) => {
    setImmediate(() => {
        callback(data, null);
    });
};

Here call the callback with data, error order .
But the order shouldn't be error, data?

promiseFn.method('hi').then(data => {
    console.log(data);
});

The promise return by the method will be rejected.
Here is a code snippet on runkit.

Promisifying all methods of a ES6 Class instance doesn't seem to work

The readme example shows it should be possible to promisify all methods in a module.

// Promisify all methods in a module
pify(fs).readFile('package.json', 'utf8').then(data => {
	console.log(JSON.parse(data).name);
	//=> 'pify'
});

This doesn't seem to work for ES6 class instances:

// test.js
const pify = require('pify');

class MyClass {
  myMethod(cb) {
    cb(null, 'hello');
  }
}

const myClass = new MyClass();
// works
myClass.myMethod((err, str) => console.log(str));
// works
pify(myClass.myMethod)().then(data => console.log(data));
// does not work
pify(myClass).myMethod().then(data => console.log(data));
$ node test.js
hello
D:\sandbox\master-project\test.js:13
pify(myClass).myMethod().then(data => console.log(data));
              ^

TypeError: pify(...).myMethod is not a function
    at Object.<anonymous> (D:\sandbox\master-project\test.js:13:15)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

I'm not sure if this is by design or not though. The example clearly says "Promisify all methods in a module", so maybe it should only be used for CommonJS modules.

Pify fails where normal new Promise() works

Hej, I am so sorry to bother you with this, I love Pify as it is saving me a lot of code when working with non-pified libraries like braintree-web and braintree-web-drop-in.

Unfortunately there is a specific method that fails in a really weird way when I pify it.

The traditional wrapper works. I was expecting pify to work wherever a wrapper works.

I know this looks really in the air and not very specific but if you could help me understand it a bit I can provide you with all other information/time needed to debug this.

Thanks so much.

  async payDropIn (dropIn: DropInInstance): Promise<any> {
    // pified version does not work, catches error, looking at the library's code
    // it seems as if some context was lost?
    return pify(dropIn.requestPaymentMethod)()
      .then((payload) => {
        alert(payload.nonce)
      })
      .catch((er) => {
        alert(er) <-- ends up here
      })

    // typical wrap works. it throws no error and alerts the string it should
    return new Promise<DropInInstance>((resolve, reject) => {
      dropIn
        .requestPaymentMethod((err, payload) => {
          if (err) {
            console.error('Error requesting payment method in drop in ui ', err)
            reject(err)
            return
          }

          alert(payload.nonce)
        })
    })
  }

Edge case: pify.all() when module is a function

I noticed that when pify promisifies a module, it stores methods in newly-created object. This means if the module is a function with attached methods (like pify itself), pify.all() promicifies methods, but loses the main function.

function fn(x, cb) {
    setImmediate(function () {
        cb(null, x);
    });
}

fn.meow = function (cb) {
    setImmediate(function () {
        cb(null, 'meow');
    });
};

var pfn = pify.all(fn);

pfn.meow().then(function (data) {
    console.log(data);
    //=> 'meow'
});

pfn('hi').then();
//=> nope, pfn is not a function

Consider supporting prototype methods

Any chance you would be willing to consider supporting pify-ing the prototypally-inherited methods of passed objects?

The main issue that I'm trying to solve is that code such as:

var lovebug = pify(new Car("Herbie"));

works if the Car constructor sets/copies/binds methods to this, but not if the methods are inherited via prototype. So, unless I've missed something, pify users must either depend on the implementation of Car or roll their own method iteration wrapper for pify (and/or pify the prototype, then pify any methods set by the constructor).

If pify-ing inherited methods sounds agreeable but you'd need a PR to consider, I can work one up. I would envision the methods being copied from the prototype to the new target object if a boolean option is set, but alternatives would be fine with me.

Thanks for considering,
Kevin

Failed to minify code with webpack 3

Webpack production build fails with error:

Failed to minify the code from this file: 
 	./node_modules/pify/index.js:3 

How to reproduce.
Create an app using react-create-app, use pify in the codebase and run yarn build

Support context

As far as I can get, right now the only way to carry the context through pify is to explicitly .bind() it:

var promise = pify(obj.method.bind(obj))(args);

This is all cool and native, but wouldn't it be more clean to make context an option:

var promise = pify(obj.method, {context: obj})(args);

Especially when one wants to save opts somewhere and pify several methods of one object?

This is more of a question than request, although I could make a PR if it souds like a good idea 😏

TypeError when passing undefined or null

Line 75 is throwing a JS error when undefined or null is passed to the function.

TypeError: Cannot convert undefined or null to object

I was going to submit a PR with this change to the line:

ret = Object.create(Object.getPrototypeOf(obj || {}));

and these tests:

test('return empty object with falsy values', t => {
	t.is(typeof m(), 'object');
	t.is(typeof m(''), 'object');
	t.is(typeof m(null), 'object');
});

But, I wasn't sure if you wanted to return the original parameter, or an empty object. And, I couldn't get the optimization test to pass

Accessing the synchronous return value of a pify'd function

Is there a way to access the synchronous return value of a pify'd function? For example:

const socket = require('dns-socket')();
const pify = require('pify');

pify(socket.query.bind(socket))({
  questions: [{ type: 'A', name: 'google.com'}]
}, 53, '8.8.8.8').then(console.log);

socket.query returns a query id, but there's no way to access it when used with pify, right?

feature: Handle function where callback is first

We encountered a situation where the callback preceded the other arguments to the function. We modified the code to include a flag to allow handling this option and are happy to contribute it if anyone is interested.

Does module.exports handle the case where input is undefined?

I have a Gatsby project that I am upgrading which requires an upgrade of Webpack from version 4 to version 5. Webpack 4 doesn't have any issues when building the app, but Webpack 5 fails on this package's compiled code. For context, one of the plugins (gatsby-plugin-sitemap) relies on pify.

The error I receive says WebpackError: TypeError: Cannot convert undefined or null to object on the following line ret = Object.create(Object.getPrototypeOf(obj));. I understand that this is the compiled/transpiled code, but is there something that needs to change in the codebase to handle an input value that comes in as undefined for any reason?

Promisifying prototype functions broken by v4

v4 changed the returned function to an arrow function from a regular one. I cannot come up with a good reason to do that, since it breaks promisifying prototypes. The prototype function cannot be bound beforehand and the arrow function forces lexical this (which is undefined). util.promisify was our workaround, since this case doesn't need the extra options of pify..

E.g. there is no way to promisify node-redis multi queries now:

// Async multi exec
redis.Multi.prototype.execAsync = pify(redis.Multi.prototype.exec);

const client = new redis.createClient(...);

// Use
const cmds = [...];
const res = await client.multi(cmds).execAsync();

// Boom: Cannot read property 'queue' of undefined

Add option to support non-error-first APIs

Initially, I didn't want this as it's a bad practise, so I asked @SamVerschueren to make a module.

After using pify for a good while, I've needed it more often than I would have liked, with a few Node.js API's (fs.exists), events, Chrome extension API's, and browser API's.

Instead of pretty much duplicating most of the code here into rfpify, I think we should add an option for it here instead.

@SamVerschueren Thoughts?

feature: all bind-all when passed an object

pify has a nice feature where you can promisify an obj's methods

example from readme:

// Promisify all methods in a module
pify(fs).readFile('package.json', 'utf8').then(data => {
	console.log(JSON.parse(data).name);
	//=> 'pify'
});

however this doesnt work when the child methods expect a reference to the owning object

example from my experience:

const Client = require('coinbase').Client
const pify = require('pify')

const client = pify(new Client({}))
const accounts = await client.getAccounts(null) // fails due to unset `this` reference

work around - use bind-all:

const Client = require('coinbase').Client
const pify = require('pify')
const bindAll = require('bind-all')

const client = pify(bindAll(new Client({})))
const accounts = await client.getAccounts(null)

Instead of multiArgs option, why not just detect multiple arguments?

Instead of having to figure out and pass { multiArgs: true } ahead of time, why not just have the internal callback check the number of arguments passed at runtime and then resolve the Promise either with an array (of multiple arguments) or the single argument itself?

Like so (shown here in ES6 syntax):

function callback(error, ...args) {
    if (error != null) {
        reject(error);
    }
    else if (args.length > 1) {
        resolve(args); // multiple
    }
    else {
        resolve(args[0]); // single
    }
}

Note that the final else-clause works correctly for either one or zero arguments, naturally passing undefined for the latter.

This would make it simple to apply pify() to an entire module where some of the callbacks supply multiple arguments and some don't. Also, this would just make pify even easier to use in general, because the right thing would be done automatically and predictably -- if the callback has multiple arguments, you will get an array from the Promise.

There is a small chance that some API that emits an array as the sole argument to some callback would appear ambiguous with the multi-argument case, but in practice I think this potential ambiguity is very small to non-existent. Most APIs always invoke callbacks in a uniform manner, and so most handlers will be hard-coded to expect an array (multiple arguments) or not (single argument). Single vs. multiple argument callback behavior is typically not "dynamic".

Move `Promise` argument into an option

Since we're soon doing a major release anyways.

pify(input, [promiseModule], [options]) => pify(input, [options])

The need to pass in a Promise fill is not very big anymore and would be better to move this into an option to clean up the interface.

losing bound this in promise

My code is

  writeRaw(address, length, buffer) {
    return pify(this.bus.i2cWrite).bind(this.bus)(address, length, buffer)
  }

but when i2cWrite is called this is undefined. this is set when this runs

  ret[key] = typeof property === 'function' && filter(key) ? processFn(property, options) : property;

but not inside the called processFn function

const processFn = (fn, options) => function (...args) { 
  // 'this' is not set here
 <...>
}

What am I doing wrong?

P.S. Sorry I don't understand bind well enough to know what happens to a bound => function. I know => uses this from outside scope, but is that overridden by bind?

EDIT: I just realized that looking at this before and during processFn means nothing. I will try to debug this better but meanwhile, can someone look at my code above?

How to use with methods of objects?

I'm using pify successfully for modules and standalone functions, but how can it be used for methods of objects? I tried it with the knox module https://www.npmjs.com/package/knox but am getting an error client.putFile is not a function even though if I remove pify and await and add back the callback(err, res) as the last argument to client.putFile it works fine.

Am I doing it wrong?

const client = pify(knox.createClient({key: '...', secret: '...', bucket: '...', port: ... }));
await client.putFile('...', '...');

The knox page shows it's normally called like this

client.putFile('my.json', '/user.json', function(err, res){
  // Always either do something with `res` or at least call `res.resume()`. 
});

Node 0.10.x support

This modules declares a non-support of Node 0.10.x in its package.json.
However, this is not true when a promise implementation is passed to it.

This produces an annoying npm warning while installing globby 3.0.0 (and probably del 2.0.0 too) on Node 0.10.x.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.