DEV Community 👩‍💻👨‍💻

Konnor Rogers
Konnor Rogers

Posted on

Converting a callback to a promise

Sometimes you want your synchronous function to run asynchronously. Perhaps you want to run multiple functions asynchronously using something like Promise.allSettled or Promise.all.

I have a number of setup functions that dont depend on each other in an application and I was curious how hard it would be to convert the setup functions to async functions without touching their internal code. (Some functions come from libraries)

The TLDR is that yes, I managed to do it.

function asPromise (callback, ...args) {
  return new Promise((resolve, reject) => {
    try {
      resolve(callback(...args))
    } catch(e) {
      reject(e)
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

Now for some examples:

function greet (greeting, name) { return "${greeting}, {name}" } 
await asPromise(greet, "hi", "konnor") 
// => "hi, konnor"
Enter fullscreen mode Exit fullscreen mode

Now what if we pass an object?

function greet ({greeting, name}) { return "${greeting}, {name}" } 
await asPromise(greet, {greeting: "hi", name: "konnor"}) 
// => "hi, konnor"
Enter fullscreen mode Exit fullscreen mode

And finally, what about an array?

function greet (ary) {
  return `${ary[0]}, ${ary[1]}`
}

await asPromise(greet, ["hi", "konnor"])
// => "hi, konnor"
Enter fullscreen mode Exit fullscreen mode

Are there edge cases? Probably. Mostly around this

if your function calls rely on this make sure to bind within the Promise like so:

await asPromise(myFunction.bind(myThis), "arg1")
Enter fullscreen mode Exit fullscreen mode

And that's all for today! Short and sweet.

Top comments (2)

Collapse
 
kettanaito profile image
Artem Zakharchenko • Edited on

Hey, Konnor! An interesting take on making a Promise wrapper around otherwise synchronous functions.

I may suggest preserving the original function's (callback) call signature.

await asPromise(() => greet('hi', 'Konnor'))
Enter fullscreen mode Exit fullscreen mode

This way your asPromise function doesn't have to forward arguments and care about the correct binding of the callback. It becomes focused on the one thing it was written to do: taking a synchronous code and making it async.

Here's the implementation for this call signature:

function asPromise(callback) {
  return new Promise((resolve, reject) => {
    try {
      const result = callback()
      resolve(result)
    } catch (error) {
      reject(error)
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

Also a nitpick: the synchronous logic you're referencing in the examples is not commonly referred to as "callback". Callback is usually referred to functions that are passed as arguments and meant to be executed later when some logic (usually async) settles.

Somehow, the callback pattern was the first thing that came to mind when I read the title of your post:

someFunc(args1, (error, data) => {
  // Callback function!
})
Enter fullscreen mode Exit fullscreen mode

I thought we're going to be converting someFunc to return a Promise instead of relying on the callback (the second argument).

Collapse
 
konnorrogers profile image
Konnor Rogers

Hey this is the stuff I was looking for! I slapped this together in about 5mins! Didn't even think to just use anonymous functions!

DEV

Thank you.

 
Thanks for visiting DEV, we’ve worked really hard to cultivate this great community and would love to have you join us. If you’d like to create an account, you can sign up here.