While writing code for a side project I stumbled upon some use cases for the recent async
/await
feature in Javascript/Node.js, which led me to write this article.
Continuing on in that project, I realized that the library I was using to interact with my database was using callback functions; this isn't the worst pattern in the world, but I was writing a wrapper class around the API and found it clunky sending the results of a query from the deeply-nested callbacks. Ultimately, it works fine, but I wanted a more elegant solution that is easier to follow.
I then got to thinking "what if there was a way that I could wrap some callback-utilizing functions in a way that would let me use asynchronously while also keeping my code DRY".
And from that spark, asyncAdapter
was born.
The Problem
Let's say your has a function that creates an XMLHttpRequest
which passes response data to a callback function, for example:
function httpGET(endpoint, callback) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function() {
if (this.readyState == 4 && this.status == 200) {
callback(JSON.parse(xhr.responseText));
}
});
xhr.open("GET", endpoint);
xhr.send();
}
It has been a trusty sidekick, but it is a little outdated and makes using retrieved data more involved than modern Javascript needs to be.
You want to use the latest and greatest APIs that tc39 and Babel can provide– like async/await
or the Promise
API– and callbacks just don't cut it.
What could you do?
My Solution
Enter asyncAdapter
. This nifty utility magically makes the function into a new Promise
-based function, allowing it to be await
-ed or otherwise handled like a Promise; this is achieved by passing in the Promise's resolve
argument where the original function's callback would go.
(Okay so it is not exactly magic, but it's still pretty cool)
Here is how you would use the example function above with asyncAdapter
:
const asyncHttpGET = asyncAdapter(httpGET, "https://example.com/api/data");
(async function someAsyncFunction() {
const data = await asyncHttpGET;
console.log(data); // -> { foo: 'bar' }
})();
The first argument to the adapter is the original function name, and the rest of the arguments constitute any arguments you would pass to the original function, in the same order.
Note that you should not pass a function in the callback parameter's position into the asyncAdapter
arguments, unless that function can return a value (e.g. not for an AJAX/Promise
-based function).
Here is an example of a non-asynchronous function being used with asyncAdapter
:
// Original function
const add = (n1, n2, callback) => callback(n1 + n2);
// Add callback function to return value
const asyncSum20 = asyncAdapter(add, 10, 10, n => n);
// Add callback function to return value with side effects
const asyncSum50 = asyncAdapter(add, 10, 10, n => n + 30);
// Use inside function to create DRY async version of original function
const asyncSum = (n1, n2, n3) => asyncAdapter(add, n1, n2, n => n + n3);
(async function someAsyncFunction() {
const sum20 = await asyncSum20;
const sum50 = await asyncSum50;
const sum100 = await asyncSum(5, 20, 75);
console.log(sum20); // -> 20
console.log(sum50); // -> 50
console.log(sum100); // -> 100
});
I have found that this implementation is fairly flexible and provides some benefits of functional programming when wrapping the adapter in a function (like the asyncSum
function above).
Do note that this may not work 100% out of the box for every third-party callback-based function; because asyncAdapter
depends on the callback argument being the last set in the function parameters, this pattern may end up being more valuable to those who can apply it to their own codebase's functions and have control over those functions' parameter order.
Conclusion
Does this sound like something you could use? Or perhaps a fun utility function to play around with?
You're in luck! I just published this to the npm registry here.
Install it with your favorite npm client...
npm i async-adapter
yarn add async-adapter
Find an bug or have an idea for a feature? File an issue or submit a pull request.
I hope you enjoy the package and find it useful. Thanks for reading!
Top comments (3)
Or you could use util.promisify without having to install a single library.
Definitely one way you could go!
asyncAdapter
can work in Node.js or browser js files, whereas util.promisify is a node-only option.True, I always forget the browser. At that point I would probably use Bluebird's
promisify
, but only because I love Bluebird. This option is a nice save for the browser.