Welcome to the final entry of my Practical Promises series! Today, we’re going to learn about the new async and await keywords that are coming as part of ES2017.
[more]
If you are just joining us, here is what you missed:
In part 1, we talked about what promises are and what they can be used for.
In part 2, we started looking at how we can create promises.
Then in part 3, we saw how each call to then actually makes a new promise, and that those promises can be chained together.
In part 4, we learned how to combine promise chaining with the creation of new promises in order to simplify complex async workflows.
In part 5, we applied everything we have learned so far to create a nice, clean API that unwraps a complex result object via promise chaining.
In part 6, we explored what happens to our promises when we call then and catch in different orders.
And in part 7, we looked at the non-standard finally
function, how we can use it to perform any post-call cleanup, and how we can emulate it using standard ES6 promises.
await
ing Promises
We’ve seen how promises can help us simplify complex, asynchronous workflows. Back in part 4, we turned this:
widgetSvc.getWidget(id).then(function(widget) {
$ctrl.widget = widget;
listingSvc.getProductListing(widget.listingId).then(function(listing) {
$ctrl.price = listing.price;
manufacturerSvc.getManufacturer(listing.manufacturer.Id).then(function(manufacturer) {
$ctrl.manufacturerName = manufacturer.name;
});
})
})
Into this:
widgetSvc.getWidget(id).then(widget => {
$ctrl.widget = widget;
return listingSvc.getProductListing(widget.listingId);
}).then(listing => {
$ctrl.price = listing.price;
return manufacturerSvc.getManufacturer(listing.manufacturer.Id);
}).then(manufacturer => {
$ctrl.manufacturerName = manufacturer.name;
});
BUT, we can do even better! If you’re familiar with C#, then you probably have at least heard of the async and await keywords. They allow us to call asynchronous code in a synchronous-looking manner. Well, guess what the JavaScript versions let you do?
async function doStuff() {
const promise = Promise.resolve(1);
const result = await promise;
console.log(`Result is: ${result}`);
}
doStuff();
That’s right! We can now write async JavaScript code without callbacks at all. Underneath, there’s still a Promise
object, but we can now await the result of a promise instead of registering a callback using then
.
NOTE: To leverage
await
, you must first decorate your function with theasync
keyword.
We can use the await
-ed result to make other async calls, too:
async function doStuff() {
const promise1 = Promise.resolve(1);
const result1 = await promise1;
const promise2 = Promise.resolve(result1 + 1);
const result2 = await promise2;
console.log(`Result is: ${result2}`);
}
doStuff();
Error-Handling with async
/await
With the Promise
class, we can use catch
to handle errors. But how do we do that with async/await? It turns out that we can just use a regular ole’ try-catch
block:
async function doStuff() {
try {
const promise = Promise.reject('Fail!');
const result = await promise;
}
catch (ex) {
console.log(`Got an error: ${ex}`)
}
}
doStuff();
Which, by the way, also works with try-catch-finally
:
async function doStuff() {
try {
const promise = Promise.reject('Fail!');
const result = await promise;
}
catch (ex) {
console.log(`Got an error: ${ex}`)
}
finally {
console.log('We are finally here!');
}
}
doStuff();
Putting it All Together
Let’s revist our original example. We have this nasty block:
widgetSvc.getWidget(id).then(function(widget) {
$ctrl.widget = widget;
listingSvc.getProductListing(widget.listingId).then(function(listing) {
$ctrl.price = listing.price;
manufacturerSvc.getManufacturer(listing.manufacturer.Id).then(function(manufacturer) {
$ctrl.manufacturerName = manufacturer.name;
});
})
})
Which we cleaned up and simplified into this:
widgetSvc.getWidget(id).then(widget => {
$ctrl.widget = widget;
return listingSvc.getProductListing(widget.listingId);
}).then(listing => {
$ctrl.price = listing.price;
return manufacturerSvc.getManufacturer(listing.manufacturer.Id);
}).then(manufacturer => {
$ctrl.manufacturerName = manufacturer.name;
});
Now, using async/await, we can clean things up even further:
$ctrl.widget = await widgetSvc.getWidget(id);
const listing = await listingSvc.getProductListing($ctrl.widget.listingId);
$ctrl.price = listing.price;
$ctrl.manufacturerName = (await manufacturerSvc.getManufacturer(listing.manufacturer.Id)).name;
How’s that for readable?
Final Thoughts
Over the last couple of months, I’ve learned a lot about promises. I hope you’ve learned a lot about them, too!
They can really help cleanup and simplify async workflows. And, thanks to the new ES2017 async/await features, we can clean things up even further!
Have you found this series of posts on promises useful? Let me know! I’m up for tackling other confusing development topics, too. Let me know what you’d like to see in either the comments or via Twitter.