kpollock
kpollock

Reputation: 3989

NodeJs: util.promisify where the callback function has multiple arguments

I may be missing something really obvious here, but how do I use util.promisify with a function which looks like this?

function awkwardFunction (options, data, callback) {
    // do stuff ...
    let item = "stuff message"
    return callback(null, response, item)
}

Which I can call like this:

 awkwardFunction ({doIt: true},'some data', (err, result, item) => {
      console.log('result')
      console.log(result)
      console.log('item')
      console.log(item)
      done()
    })

And get back

result
{ data: 'some data' }
item
stuff message

When using the promisified version:

let kptest = require('util').promisify(awkwardFunction)
kptest({doIt: true},'some data')
   .then((response, item) => {
    console.log('response')
    console.log(response)
    console.log('item')
    console.log(item)

 })
 .catch(err => {
     console.log(err)
  })

and trying to access both "response" and "item", it seems the 2nd param is ignored...

result
{ data: 'some data' }
item
undefined

Is there a way to do this WITHOUT changing the function (in reality, it is a library function, so I can't).

Upvotes: 21

Views: 12223

Answers (5)

Aidin
Aidin

Reputation: 30097

I was just rollling up my sleeves for an open heart surgery to achieve this, but I am glad I found someone has already done this.

If you use Bluebird's Promisify (it's getting so popular) then there actually is a flag of { multiArgs: true } that you can pass and will do exactly what you need here! (Source)

It turns multiple arguments of callback into an array. So, in my case for MySQL's query that the default callback has 3 arguments of (error, result, fields), getting fields is not possible with typical promisify. But with that {multiArgs: true} flag being passed, the resolved value will become an array of [result, fields].

Upvotes: 4

kpollock
kpollock

Reputation: 3989

I can't decide which approach I like the best - all 3 answers are great. Yury Tarabanko's is probably the most "standard", Alex G's is nicely generic, and estus's super simple.

I don't want to leave this question "Unanswered" because that is not true, and not useful for others looking for the same information.

If there is a better way t handle this, please can the moderators let me know!

Upvotes: 1

Estus Flask
Estus Flask

Reputation: 222935

util.promisify is intended to be used with Node-style callbacks with function (err, result): void signature.

Multiple arguments can be treated manually:

let kptest = require('util').promisify(
  (options, data, cb) => awkwardFunction(
    options,
    data,
    (err, ...results) => cb(err, results)
  )
)

kptest({doIt: true},'some data')
.then(([response, item]) => {...});

In case more sophisticated functionality is wanted, some third-party solution like pify can be used instead of util.promisify, it has multiArgs option to cover this case.

Upvotes: 29

Alex G
Alex G

Reputation: 1917

You could make your own promisify, where you return a promise that resolves with the arguments of the callback and on the then block you destructure them. Hope this helps.

function awkwardFunction (options, data, callback) {
    // do stuff ...
    let item = "stuff message";
    return callback(null, data, item);
}

const mypromisify = (fn) =>
    (...args) =>
        new Promise(resolve =>
            fn(...args,
                (...a) => resolve(a)
            )
        );

const kptest = mypromisify(awkwardFunction);

kptest({ doIt: true }, 'some data')
    .then(([error, response, item]) => {
        console.log(response);
        console.log(item);
    })
    .catch(err => {
        console.log(err);
    });

Upvotes: 6

Yury Tarabanko
Yury Tarabanko

Reputation: 45106

It is not possible to have .then((response, item) => { because a promise represents single value. But you could have it like this .then(({response, item}) => { an object w/ two fields.

You'll need to provide a custom promisify implementation for the function.

const { promisify } = require('util')

awkwardFunction[promisify.custom] = (options, data) => new Promise((resolve, reject) => {
  awkwardFunction(options, data, (err, response, item) => {
    if(err) { reject(err) }
    else { resolve({ response, item }) }
  })
})

const kptest = promisify(awkwardFunction)

Or if this is the only place where the function is promisified you could use the promisified version directly const kptest = (options, data) => new Promise(... w/o additional promisification step.

Upvotes: 4

Related Questions