try-catch-FAIL

Failure is inevitable

NAVIGATION - SEARCH

Practical Promises in JavaScript - Using async-await

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.

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.

awaiting 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 the async 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.

About Matt Honeycutt...

Matt Honeycutt is a software architect specializing in ASP.NET web applications, particularly ASP.NET MVC. He has over a decade of experience in building (and testing!) web applications. He’s an avid practitioner of Test-Driven Development, creating both the SpecsFor and SpecsFor.Mvc frameworks.

He's also an author for Pluralsight, where he publishes courses on everything from web applications to testing!

blog comments powered by Disqus