Wait Asyncy: Inline conversion of callbacks to async/await

One of the latest hot bits of news about Node.js is the built-in support for turning callback based functions into Promise returning functions using util.promisify in v8.0.0. As is often the case with newer features, Dr. Axel Rauschmayer has a great article on 2ality.

When the Promisefirst came to JavaScript, I really appreciated the clarity its use could bring to asynchronous callback code. That being said, the Promise may help eliminate callback He**, but it introduces its own kind of Purgatory. A newer addition to JavaScript,async/await, now available in all major browsers and Node.js, can bring an additional level of clarity. Albeit, this is at the expense of some risks.

Pondering the above last week, I came-up with my weekend coding challenge. How can I do for async/await what util.promisify does for Promiseand also reduce the risk of Node.js exits? Out of this came asyncy, a utility for almost transparently converting any function, callback based or not, intoasync/await calls right inline. Not only is this useful for reducing callback He** and Promise Purgatory, it can also:

  1. Reduce the risk of un-handled Promise rejections that will cause a lot of Node.js apps to start failing in the future, “ In future versions of Node.js unhandled promise rejections will cause code execution to exit with non-zero exit code.”
  2. Support the migration of a synchronous code base to an asynchronous one.

Turning fs.readFile call into an async/awaitcall using asyncy is easy:

const {err,result,args} = await asyncy.inline(fs,fs.readFile,"f.txt")

Note the use of a de-structured return object. De-structuring is also supported in all major browsers and is described well on MDN. The values of err, result,and args can all be checked on subsequent lines, e.g.:

const {err,result,args} = await asyncy.inline(fs.readFile,"f.txt");
if(err) ... handle error ...
console.log(result[0],"allocated"); // prints fd allocated

The asyncy.inline code is implemented so as to try and catch errors for return, reducing the risk of a Node.js exit, but also making the core application code somewhat easier to read. And, if you are just prototyping, you might even chose to ignore the errors in the short term. Node crashes do not make prototyping easy, but they will happen less with this approach.

Note that result is an array, which will normally be of length 1. However, in the case that the function being in-lined expects a callback with more arguments, the array will be longer.

As an extra goody to support debugging, the returned object also contains a field, args, holding the arguments used to produce the result or generate the error.

By default, asyncy.inline assumes the normal callback signature of cb(err,result). What if someone (not you off-course) has written a really complex callback and you don’t want to move the logic into your core code base? No problem! If asyncy.inline detects a function as the last argument, it will assume it is a callback and invoke it prior to returning. The below code can also be used:

const {err,result,args} = await
asyncy.inline(fs,fs.readFile,"f.txt",
(err,fd) => { console.log(fd,"allocated");});

In this case, the callback is invoked with its normal signature rather arguments that have to be extracted out of an object.

I hope you found this useful. Recommend it or re-post it if you did. For more information on how to use asyncy to redefine functions out-of-line or asynchronize entire objects, visit the repository on GitHub.