JavaScript Async functions compared to Promises


Example

async functions do not replace the Promise type; they add language keywords that make promises easier to call. They are interchangeable:

async function doAsyncThing() { ... }

function doPromiseThing(input) { return new Promise((r, x) => ...); }

// Call with promise syntax
doAsyncThing()
    .then(a => doPromiseThing(a))
    .then(b => ...)
    .catch(ex => ...);

// Call with await syntax
try {
    const a = await doAsyncThing();
    const b = await doPromiseThing(a);
    ...
}
catch(ex) { ... }

Any function that uses chains of promises can be rewritten using await:

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

The function can be rewritten using async / await as follows:

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

This async variant of newUnicorn() appears to return a Promise, but really there were multiple await keywords. Each one returned a Promise, so really we had a collection of promises rather than a chain.

In fact we can think of it as a function* generator, with each await being a yield new Promise. However, the results of each promise are needed by the next to continue the function. This is why the additional keyword async is needed on the function (as well as the await keyword when calling the promises) as it tells Javascript to automatically creates an observer for this iteration. The Promise returned by async function newUnicorn() resolves when this iteration completes.

Practically, you don't need to consider that; await hides the promise and async hides the generator iteration.

You can call async functions as if they were promises, and await any promise or any async function. You don't need to await an async function, just as you can execute a promise without a .then().

You can also use an async IIFE if you want to execute that code immediately:

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