JavaScript Language


Async functions (async/await) 8

1
2
3
E4X
5
5.1
6
7
8

This draft deletes the entire topic.

inline side-by-side expand all collapse all

Examples

  • 8

    A function defined as async is a function that can perform asynchronous actions but still look synchronous. The way it's done is using the await keyword to pause the function while it waits for a Promise to resolve/reject.

    Note: Async functions are a stage 3 proposal and are not yet integrated into the ECMAScript standard.

    async function getJSON(url) {
        const response = await fetch(url);
        return response.json();
    }
    

    An async function always returns a Promise itself, so you can use it in other asynchronous functions.

  • 10

    You have to keep the operator precedence in mind when using await keyword.

    Imagine that we have an asynchronous function getUnicorn() which returns a Promise that resolves to an instance of class Unicorn. Now we want to get the size of the unicorn using the getSize() method of that class.

    Look at the following code:

    await getUnicorn().getSize()
    

    At first sight it looks valid, but actually it's not. Due to operator precedence, it's equivalent to the following:

    await (getUnicorn().getSize())
    

    Here we attempt to call getSize() method of the Promise object, which isn't what we want.

    Instead, we should use brackets to denote that we first want to wait for the unicorn, and then call getSize() method of the result:

    (await getUnicorn()).getSize()
    

    Of course the previous version could be valid in some cases, for example if the getUnicorn() function was synchronous, but the getSize() method was asynchronous.

  • 7

    Consider following function using promises:

    function newUnicorn() {
      return fetch('unicorn.json')            // fetch unicorn.json from server
      .then(response => response.json())      // parse the response as JSON
      .then(unicorn =>
        fetch('new/unicorn', {                // send a request to 'new/unicorn' 
            method: 'post',                   // using POST method
            body: JSON.stringify({unicorn})   // pass the unicorn to the request body
        })
      )
      .then(response => response.json())
      .then(response => response.success)     // return success property of response
      .catch(err => console.log('Error creating unicorn: ' + err));
     }
    

    It can be rewritten using async / await like that:

    async function newUnicorn() {
      try {
        const unicorn = (await fetch('unicorn.json')).json()
        const response = (
          await fetch('new/unicorn', {
              method: 'post',
              body: JSON.stringify({unicorn})
          })
        ).json()
        return response.success
      } catch (err) {
        console.log('Error creating unicorn: ' + err);
      }
    }
    

    Basically, placing the await keyword allows you to use asynchronous functions (that is, functions that returns promises) as if they were synchronous. Note that you can only use await in async functions. You can also use async IIFE if you want to execute that code immediately:

    (async () => {
      await makeCoffee()
      console.log('coffee is ready!')
    })()
    

I am downvoting this example because it is...

Syntax

  • async function foo() {
       ...
       await asyncCall()
    }
  • async function() { ... }
  • async() => { ... }

Parameters

Parameters

Remarks

Async functions are a sugar over promises. They help you make your code more readable and more linear-looking, with less levels of indentation. They do not, however, replace the Promise type, and the lowest level function still probably need to be written with the Promise constructor.

This is not yet a definitive feature of the language, it is currently in stage 3 (candidate) status , this means:

The proposal is mostly finished and now needs feedback from implementations and users to progress further.

Source

Still have question about Async functions (async/await)? Ask Question

Introduction

8

A function defined as async is a function that can perform asynchronous actions but still look synchronous. The way it's done is using the await keyword to pause the function while it waits for a Promise to resolve/reject.

Note: Async functions are a stage 3 proposal and are not yet integrated into the ECMAScript standard.

async function getJSON(url) {
    const response = await fetch(url);
    return response.json();
}

An async function always returns a Promise itself, so you can use it in other asynchronous functions.

Await and operator precedence

10

You have to keep the operator precedence in mind when using await keyword.

Imagine that we have an asynchronous function getUnicorn() which returns a Promise that resolves to an instance of class Unicorn. Now we want to get the size of the unicorn using the getSize() method of that class.

Look at the following code:

await getUnicorn().getSize()

At first sight it looks valid, but actually it's not. Due to operator precedence, it's equivalent to the following:

await (getUnicorn().getSize())

Here we attempt to call getSize() method of the Promise object, which isn't what we want.

Instead, we should use brackets to denote that we first want to wait for the unicorn, and then call getSize() method of the result:

(await getUnicorn()).getSize()

Of course the previous version could be valid in some cases, for example if the getUnicorn() function was synchronous, but the getSize() method was asynchronous.

Async functions compared to Promises

7

Consider following function using promises:

function newUnicorn() {
  return fetch('unicorn.json')            // fetch unicorn.json from server
  .then(response => response.json())      // parse the response as JSON
  .then(unicorn =>
    fetch('new/unicorn', {                // send a request to 'new/unicorn' 
        method: 'post',                   // using POST method
        body: JSON.stringify({unicorn})   // pass the unicorn to the request body
    })
  )
  .then(response => response.json())
  .then(response => response.success)     // return success property of response
  .catch(err => console.log('Error creating unicorn: ' + err));
 }

It can be rewritten using async / await like that:

async function newUnicorn() {
  try {
    const unicorn = (await fetch('unicorn.json')).json()
    const response = (
      await fetch('new/unicorn', {
          method: 'post',
          body: JSON.stringify({unicorn})
      })
    ).json()
    return response.success
  } catch (err) {
    console.log('Error creating unicorn: ' + err);
  }
}

Basically, placing the await keyword allows you to use asynchronous functions (that is, functions that returns promises) as if they were synchronous. Note that you can only use await in async functions. You can also use async IIFE if you want to execute that code immediately:

(async () => {
  await makeCoffee()
  console.log('coffee is ready!')
})()

Looping with async await

6

When using async await in loops, you might encounter some of these problems.

If you just try to use await inside forEach, this will throw an Unexpected token error.

(async() => {
 data = [1, 2, 3, 4, 5];
 data.forEach(e => {
   const i = await somePromiseFn(e);
   console.log(i);
 });
})();

This comes from the fact that you've erroneously seen the arrow function as a block. The await will be in the context of the callback function, which is not async.
The interpreter protects us from making the above error, but if you add async to the forEach callback no errors get thrown. You might think this solves the problem, but it won't work as expected.

Example:

(async() => {
  data = [1, 2, 3, 4, 5];
  data.forEach(async(e) => {
    const i = await somePromiseFn(e);
    console.log(i);
  });
  console.log('this will print first');
})();

This happens because the callback async function can only pause itself, not the parent async function.

You could write an asyncForEach function that returns a promise and then you could something like await asyncForEach(async (e) => await somePromiseFn(e), data ) Basically you return a promise that resolves when all the callbacks are awaited and done. But there are better ways of doing this, and that is to just use a loop.

You can use a for-of loop or a for/while loop, it doesn't really matter which one you pick.

(async() => {
  data = [1, 2, 3, 4, 5];
  for (let e of data) {
    const i = await somePromiseFn(e);
    console.log(i);
  }
  console.log('this will print last');
})();

But there's another catch. This solution will wait for each call to somePromiseFn to complete before iterating over the next one.
This is great if you actually want your somePromiseFn invocations to be executed in order but if you want them to run concurrently, you will need to await on Promise.all.

(async() => {
 data = [1, 2, 3, 4, 5];
 const p = await Promise.all(data.map(async(e) => await somePromiseFn(e)));
 console.log(...p);
})();

Promise.all receives an array of promises as its only parameter and returns a promise. When all of the promises in the array are resolved, the returned promise is also resolved. We await on that promise and when it's resolved all our values are available.

The above examples are fully runnable. The somePromiseFn function can be made as an async echo function with a timeout. You can try out the examples in the babel-repl with at least the stage-3 preset and look at the output.

function somePromiseFn(n) {
 return new Promise((res, rej) => {
   setTimeout(() => res(n), 250);
 });
}

Less indentation

5

With promises:

function doTheThing() {
    return doOneThing()
        .then(doAnother)
        .then(doSomeMore)
        .catch(handleErrors)
}

With async functions:

async function doTheThing() {
    try {
        const one = await doOneThing();
        const another = await doAnother(one);
        return await doSomeMore(another);
    } catch (err) {
        handleErrors(err);
    }
}

Note how the return is at the bottom, and not at the top, and you use the language's native error handling mechanics (try/catch)

Topic Outline