Javascript async await tutorial

Mar 31, 2019

Async Await

Promises

This article builds on the understanding of the concept of promises in Javascript. If you are not familiar with them or need a quick recap, check the following article.

Javascript promises tutorial

Promises are a useful, modern technique in javascript to handle async behavior and a good alternative to callbacks.

Async

Let's assume we have a simple function returning some value

function foo() {
    return 42;
}

Changing it to async function is pretty simple. You just add async at the beginning like this:

async function foo() {
    return 42;
}

Pretty easy right? What did actually change though?

The async function now returns a promise. Instead of returning just 42 as in the previous example, it returns Promise { 42 }, which is fulfilled.

async function foo() {
    return 42;
}

foo().then(result => {
    console.log(result); //42
})

In case that there is an unhandled error in the async function, it returns rejected promise with that error. You can handle it as usual with catch() clause.

async function foo() {
    throw new Error("Oh dear! It's broken!");
}

foo().then(result => {
    console.log(result); // This is not called because of the error
}).catch(error => {
    console.log(error); // Error: Oh dear! It's broken!
});

Async in arrow functions and class methods

Even though our example was on regular function(), you can, of course, use the same concept for arrow functions:

const foo = async (bar) => {
  ...
}

It works even for class methods:

class Foo {
  async bar() {
    ...
  }
}

Await

Await syntax is simple you just put await before your promise:

await myPromise;

What it does is that it waits for the promise to resolve and if successful it returns the value. The execution of the program does not continue to the next line until the promise is settled.

const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("Completed"), 1000)
});

async function foo() {
    const result = await myPromise; // Wait here until the promise settles
}

As I already mentioned, you can use await only in a function which is marked as async. Otherwise, you get an error:

SyntaxError: await is only valid in async function

One implication of this is that you cannot use await in the top-level code.

Multiple calls

Of course, the example above is rather simple. Where await really shines is multiple subsequent async calls, where each call depends on the value returned by the previous one.

const first = await firstCall();
const second = await secondCall(first);
const third = await  thidrCall(second);

It is a concise, easy to read way to represent multiple async calls. With promises, it would be not so nice chain of then() clauses, which is not so nice. Of course, still much better than chained callbacks.

Error handling

When working with promises, you can provide then() clause, which handles the case where the promise is fulfilled successfully and catch() clause to handle any errors when the promise is rejected. There is even finally() clause which executes no matter whether the promise was fulfilled or rejected.

foo()
.then(successFunction)
.catch(failureFunction)
.finally(doThisNoMatterWhatFunction);

How can you do the same with await though? It just covers the case of then() where the promise is fulfilled. How do you do error handling and possibly finally() functionality?

The good news is that you can use the good old try-catch-finally:

async function foo() {
    try {
        const result = await myPromise;
    } catch (error) {
        // Error handling
    } finally {
        // After try/catch is done,
        // do this whether there was error or not
    }
}

That means - try block executes first. When there is an error, the execution of try block terminates and it jumps to the catch block. No matter whether there was an error or not, in the end, the finally block executes.

Traditional try-catch-finally has some advantages over chained promises with then()-catch()-finally().

It is arguably easier to read. More importantly, it is a plain old traditional JavaScript construct, which everybody knows. You no longer need to use one error handling concept for promises and a different one for non-promise code. Your error handling is unified and cleaner. In one try block you can have mixed async and non-async code.

Waiting for multiple parallel promises

Sometimes instead of resolving async functions one after another, it is useful to execute them in parallel and await until all of them are finished.

With promises, you can use Promise.all(). As input, you provide an array of promises. It returns a single promise, which resolves once all the passed in promises are fulfilled.

Promise.all([promise1, promise2, promise3]).then(doSomething);

Since Promise.all() returns a promise, you can still use await instead of then:

const results = await Promise.all([promise1, promise2, promise3]);

Compatibility

The async-await functionality is well supported in all the major modern browsers. Of course, you'll be in trouble with Internet Explorer. It does not support async await at all, but you can work around this.

Async compatibility

Conclusion

Async-await offers an easy way to write asynchronous code as if it was synchronous. It is easier to read and write.

Await can be used before an asynchronous call returning a promise. The execution of the function will wait until the call is resolved and only then continue to the next line.

Since await replaces then().catch() chains, you cannot use the same error handling like with promises. Instead, you should use the good old try-catch-finally.

Await can be only used in functions marked as async. Marking function as async makes it also return a promise. This is done automatically for you, so you don't need to return a promise explicitly.


Tagged with:
Vojtech Ruzicka

Get notifications about new posts on Twitter, RSS or Email.