Async/Await in Node.js and the Async IIFE
Monday, January 4, 2016
The async
and await
keywords revolutionized asynchronous programming in .NET when they were added to C# 5 in 2012. The keywords and the async/await pattern are on track to be added to ES2016.
One of the best ways to get a taste of what it'll be like in ECMAScript is to use TypeScript. TypeScript added support for async/await in version 1.7. Currently it's only supported when compiling to ES2015 (it relies on generators), so the best way to test it out is in a Node.js app (examples here run on Node 5.3.0).
Promises
Promises were introduced in ES2015 and are a lot like tasks in .NET (technically .NET tasks are futures, not promises). And just like how async/await simplifies working with tasks in .NET, async/await in ECMAScript/TypeScript simplifies working with promises.
More can be found on promises here.
I was recently testing out various client proxies generated from Swagger. I generated a TypeScript/Node.js client using the Swagger codegen tool and their sample pet store API definition. The generated client uses promises, so it was perfect for testing out async/await.
Before Async/Await
Before we use async/await, let's see what the code looks like if we used strictly promises and the then()
callback. We're going to make an API call to get inventory information; and if there are any available pets, we'll make a second API call to retieve them...
try {
storeApi.getInventory().then(inventoryResponse => {
let inventorySummary = inventoryResponse.body;
console.log('Pet Statuses:');
for (let status of Object.keys(inventorySummary)) {
console.log(`${status} - ${inventorySummary[status]}`);
}
if (inventorySummary[AVAILABLE] > 0) {
petApi.findPetsByStatus(AVAILABLE).then(petResponse => {
let availablePets = petResponse.body;
console.log('\n\nAvailable Pets:');
availablePets.forEach(p => console.log(`${p.id} - ${p.name}`));
}).catch(e => {
// if promise from findPetsByStatus() is rejected or exception thrown in callback
});
}
}).catch(e => {
// if promise from getInventory() is rejected or exception thrown in callback
});
} catch (e) {
// does this catch anything?
}
There are at least 3 places where we might need to handle errors. The callbacks also make the code a bit hard to read and reason about. If you're used to JavaScript programming with lots of callbacks or promises, this might not seem so bad. But let's see how async/await can make things better.
Async/Await and the Async IIFE
The await
keyword can only be used inside of a function marked with the async
keyword. Since the code we're executing is not currently in a function, we'll have to put it inside of one. One way to do this is with an "async IIFE" (immediately invoked function expression)...
(async () => {
// code goes here
})();
Now let's rewrite the code using callbacks with async/await...
(async () => {
try {
let {body: inventorySummary} = await storeApi.getInventory();
console.log('Pet Statuses:');
for (let status of Object.keys(inventorySummary)) {
console.log(`${status} - ${inventorySummary[status]}`);
}
if (inventorySummary[AVAILABLE] > 0) {
let {body: availablePets} = await petApi.findPetsByStatus(AVAILABLE);
console.log('\n\nAvailable Pets:');
availablePets.forEach(p => console.log(`${p.id} - ${p.name}`));
}
} catch (e) {
// this should catch all exceptions
}
})();
Now the asynchronous calls look just like synchronous code. We're able to use one try/catch block to catch all exceptions that are thrown.
Using await
in the API calls also allows us to use the object destructuring syntax to further simplify our code. Note that as of Node 5.3.0, destructuring is still hidden behind a flag, so we'll have to enable it on the command line:
$ node --harmony_destructuring app.js
To see the difference side-by-side, take a look at this diff.
And go here for the full source code:
https://github.com/anthonychu/async-await-typescript-node
The async
and await
keywords revolutionized asynchronous programming in .NET when they were added to C# 5 in 2012. The keywords and the async/await pattern are on track to be added to ES2016.
One of the best ways to get a taste of what it'll be like in ECMAScript is to use TypeScript. TypeScript added support for async/await in version 1.7. Currently it's only supported when compiling to ES2015 (it relies on generators), so the best way to test it out is in a Node.js app (examples here run on Node 5.3.0).
Promises
Promises were introduced in ES2015 and are a lot like tasks in .NET (technically .NET tasks are futures, not promises). And just like how async/await simplifies working with tasks in .NET, async/await in ECMAScript/TypeScript simplifies working with promises.
More can be found on promises here.
I was recently testing out various client proxies generated from Swagger. I generated a TypeScript/Node.js client using the Swagger codegen tool and their sample pet store API definition. The generated client uses promises, so it was perfect for testing out async/await.
Before Async/Await
Before we use async/await, let's see what the code looks like if we used strictly promises and the then()
callback. We're going to make an API call to get inventory information; and if there are any available pets, we'll make a second API call to retieve them...
try {
storeApi.getInventory().then(inventoryResponse => {
let inventorySummary = inventoryResponse.body;
console.log('Pet Statuses:');
for (let status of Object.keys(inventorySummary)) {
console.log(`${status} - ${inventorySummary[status]}`);
}
if (inventorySummary[AVAILABLE] > 0) {
petApi.findPetsByStatus(AVAILABLE).then(petResponse => {
let availablePets = petResponse.body;
console.log('\n\nAvailable Pets:');
availablePets.forEach(p => console.log(`${p.id} - ${p.name}`));
}).catch(e => {
// if promise from findPetsByStatus() is rejected or exception thrown in callback
});
}
}).catch(e => {
// if promise from getInventory() is rejected or exception thrown in callback
});
} catch (e) {
// does this catch anything?
}
There are at least 3 places where we might need to handle errors. The callbacks also make the code a bit hard to read and reason about. If you're used to JavaScript programming with lots of callbacks or promises, this might not seem so bad. But let's see how async/await can make things better.
Async/Await and the Async IIFE
The await
keyword can only be used inside of a function marked with the async
keyword. Since the code we're executing is not currently in a function, we'll have to put it inside of one. One way to do this is with an "async IIFE" (immediately invoked function expression)...
(async () => {
// code goes here
})();
Now let's rewrite the code using callbacks with async/await...
(async () => {
try {
let {body: inventorySummary} = await storeApi.getInventory();
console.log('Pet Statuses:');
for (let status of Object.keys(inventorySummary)) {
console.log(`${status} - ${inventorySummary[status]}`);
}
if (inventorySummary[AVAILABLE] > 0) {
let {body: availablePets} = await petApi.findPetsByStatus(AVAILABLE);
console.log('\n\nAvailable Pets:');
availablePets.forEach(p => console.log(`${p.id} - ${p.name}`));
}
} catch (e) {
// this should catch all exceptions
}
})();
Now the asynchronous calls look just like synchronous code. We're able to use one try/catch block to catch all exceptions that are thrown.
Using await
in the API calls also allows us to use the object destructuring syntax to further simplify our code. Note that as of Node 5.3.0, destructuring is still hidden behind a flag, so we'll have to enable it on the command line:
$ node --harmony_destructuring app.js
To see the difference side-by-side, take a look at this diff.
And go here for the full source code: https://github.com/anthonychu/async-await-typescript-node