Refactoring callbacks to async/await
Web browsers have a few functions that accept callback parameters. setTimeout
and requestAnimationFrame
are the first that come to mind.
If you need to do multiple calls to these functions in a row, you quickly end up in callback hell. Here's a quick tip to flatten your code with async/await.
This is extracted from an actual piece of code I was working on last week:
function callbackHell() { // A requestAnimationFrame(() => { requestAnimationFrame(() => { // B setTimeout(() => { // C }, 1000); }); });}
Before you know it, your code is nested 3 or more levels deep.
One thing these functions with callback parameters have in common, is that they use the callback to "run something after waiting for something else". Sounds like a great case for async/await!
First we need to make callbackHell
async
to enable the await
keyword inside the function.
async function callbackHell() { // A requestAnimationFrame(() => { requestAnimationFrame(() => { // B setTimeout(() => { // C }, 1000); }); });}
Next, we'll create a wrapper function around requestAnimationFrame
to make it awaitable. Any function that returns a promise is awaitable.
function nextFrame() { return new Promise(resolve => { requestAnimationFrame(() => { resolve(); }); });}
This can be shortened to a (kind of) oneliner:
function nextFrame() { return new Promise(requestAnimationFrame);}
Now that nextFrame
returns a promise, we can await
it.
async function callbackHell() { // A await nextFrame(); await nextFrame(); // B setTimeout(() => { // C }, 1000);}
Let's do the same with setTimeout
.
function timeout(timeout) { return new Promise(resolve => { setTimeout(() => { resolve(); }, timeout); });}
Once again, a bit shorter:
function timeout(timeout) { return new Promise(resolve => setTimeout(resolve, timeout));}
And our final callbackHell
function:
async function callbackHell() { // A await nextFrame(); await nextFrame(); // B await timeout(1000); // C}
No more callback hell, just one straight line!
Caveats
Refactoring to async/await only works if you don't care about the return value of these functions.
For example, setTimeout
returns an ID that can be used to cancel the timeout.
const timeout = setTimeout(() => {}, 1000); clearTimeout(timeout);
This piece of code can't be refactored to async/await, since await
only returns a value after the timeout has completed.