The await operator

#The await operator

The await operator is one of the most useful things to happen to JavaScript in recent years. But why is it so useful? To find out, let’s take a look at an example.

///main.js
async function main() {
  // Get a promise to a number
  let pricePromise = getPricePromise('DOGE')

  let price = await(pricePromise)

  // Log the outcome
  console.log('DOGE price: ', price)
}

main()
///index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Untitled App</title>
  </head>
  <body>
    <div id="root"></div>
    <script>
      // Get a promise to the price for the specified ticker
      function getPricePromise(ticker) {
        return simulate("getting price", () =>
          Math.random() * (ticker === 'DOGE' ? 10 : 1000)
        )
      }

      // Return a promise that resolves in a random amount of time. 
      // This can be used to simulate network or disk activity.
      const simulate = (message, getter) =>
        new Promise((resolve, reject) => {
          let delay = Math.random() * 1000
          setTimeout(() => {
              if (Math.random() < setErrorProbability.probability) {
                  reject(`An error occured while ${message}.`)
              }
              else {
                  resolve(getter())
              }
          }, delay)
        })

      function setErrorProbability(probability) {
          setErrorProbability.probability = probability
      }
    </script>
    <script type="module" src="main.js"></script>
  </body>
</html>

You can probably guess what await does, just by looking at the above example:

When the await operator is passed a promise, it pauses the function until the promise resolves, and then it returns the promise’s result.

Await is a lot like promise.then(); they both wait for a promise to resolve and then make the outcome available to subsequently executed code. But unlike promise.then(), the await operator doesn’t require that you create a separate function to handle the outcome. Instead, it just returns it. Neat, huh?

But putting await aside for the moment, there’s something a little weird about the above example: why is everything wrapped inside a main function?

#Await is asynchronous

The await operator can’t be used in most JavaScript code. In fact, the only place where await can be used is within async functions. But why this limitation?

When JavaScript encounters an await operator, it needs to stop and wait until the given promise resolves. But this presents a problem: JavaScript can’t multitask; if it stops execution to wait for a response, the whole browser will stop responding!

To get around this, the await operator doesn’t really wait for a response. It just stops executing that particular function, and immediately continues on from the point where the function was called. You can confirm this for yourself by comparing the order of the console messages in the following example with their locations in the corresponding code; note how the final line of main() runs after the final line of the script logs “Done!” to the console.

///main.js
async function main() {
  console.log("Getting quote from hitchhiker's guide...")

  let quotePromise = getQuote()
  let quote = await(quotePromise)

  console.log(quote)
}

main()
console.log("Done!")
///index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Untitled App</title>
  </head>
  <body>
    <div id="root"></div>
    <script>
      const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

      function getQuote() {
        return delay(100).then(() => "Don't Panic.")
      }

      function setErrorProbability(probability) {
          setErrorProbability.probability = probability
      }
    </script>
    <script type="module" src="main.js"></script>
  </body>
</html>

Ok, so await pauses the current function without stopping the entire script. But this still doesn’t explain why you’d need a special type of function just for await. After all, wouldn’t the above script make just as much sense without the async keyword?

But consider this: what would happen if your async function returned a result after the rest of the script had finished executing?

async function getQuote() {
  // Wait for 100ms to pass to simulate network activity
  await delay(100)

  return (
    "Since we decided a few weeks ago to adopt the leaf as legal " +
    "tender, we have, of course, all become immensely rich."
  )
}

let quote = getQuote()

// This line will be executed before `getQuote()` finishes executing,
// so `quote` can't contain the returned quote!
console.log(quote)

This async function’s return statement won’t be reached until after the script’s final console.log() has already been executed. So how could the console.log() access the returned quote? The answer, of course, is via a promise!

///main.js
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

async function getQuote() {
  await delay(100)

  return (
    "It is a mistake to think you can solve any major problems " +
    "just with potatoes."
  )
}

getQuote().then(quote => {
  console.log(quote)
})

Because the await operator doesn’t return a result until some point in the future, any functions that use await can only have their result accessed via a promise. And that’s why async functions are the only place where await operators can be used: async functions are guaranteed to return a promise!

#Awaiting non-promises

The await operator works with any value that you pass to it; it isn’t limited to promises. If you happen to pass something that’s not a promise, it’ll just return the value as is.

///main.js
async function printQuote() {
  let quote = await(
    "The ships hung in the sky in much the same way that bricks don’t."
  )

  console.log(quote)
}

printQuote()

However, if you do use await with non-promises, keep in mind that await will never return immediately. Even if you await a normal value or an already-resolved promise, it will pause the function and wait until the rest of the script has completed execution — just like promise.then().

///main.js
async function printQuote() {
  // Even if you pass `await` a non-promise, it'll still wait for
  // the rest of the script to finish executing.
  let quote = await(
    "A common mistake that people make when trying to design " +
    "something completely foolproof is to underestimate the " +
    "ingenuity of complete fools."
  )

  console.log(quote)
}

console.log("Getting quote from hitchhiker's guide...")
printQuote()
console.log("Done.")

#Parenthesis and precedence

One special thing about await is that it’s an operator. This means that you can use it without parenthesis.

///main.js
async function printRandomPrice() {
  console.log('BTC price: ', await getPricePromise('BTC'))
}

printRandomPrice()
///index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Untitled App</title>
  </head>
  <body>
    <div id="root"></div>
    <script>
      // Get a promise to the price for the specified ticker
      function getPricePromise(ticker) {
        return simulate("getting price", () =>
          Math.random() * (ticker === 'DOGE' ? 10 : 1000)
        )
      }

      // Return a promise that resolves in a random amount of time. 
      // This can be used to simulate network or disk activity.
      const simulate = (message, getter) =>
        new Promise((resolve, reject) => {
          let delay = Math.random() * 1000
          setTimeout(() => {
              if (Math.random() < setErrorProbability.probability) {
                  reject(`An error occured while ${message}.`)
              }
              else {
                  resolve(getter())
              }
          }, delay)
        })

      function setErrorProbability(probability) {
          setErrorProbability.probability = probability
      }
    </script>
    <script type="module" src="main.js"></script>
  </body>
</html>

Most code that you’ll see out in the real world doesn’t use parentheses, so I’ll be leaving them out of most of the remaining examples. However, there is one situation in which parenthesis come in handy.

Await expressions can be used anywhere within your code. They can be used within function calls, within object or array literals, or even as conditions within for or while loops. In fact, you can even dive into an awaited result using . notation. But doing so without the parenthesis will lead to weeping and gnashing of teeth.

///main.js
async function fetchQuote() {
  return "Forty-two"
}

async function printQuoteLength() {
  let length = await fetchQuote().length

  // Huh?! Why is the length `undefined`?
  console.log(length)
}

printQuoteLength()
///solution:main.js
async function fetchQuote() {
  return "Forty-two"
}

async function printQuoteLength() {
  let length = (await fetchQuote()).length

  // The length of forty-two is nine. This makes far more sense.
  console.log(length)
}

printQuoteLength()

You’ve probably figured out what’s happening in the above example: the . operator has a higher precedence than the await operator, and so await is being applied to fetchQuote().length. Because fetchQuote() is an async function, it returns a promise, and as promises don’t have a length property, the whole expression reduces to await undefined.

See if you can fix the above example by adding a set of parenthesis. And once you’ve given it a try, take a look at the solution to see if you’ve added them in the same place as I have.

#When things go wrong

So to summarize: when you pass the await operator a promise, it waits for the promise to resolve, and then returns the successful outcome. And that’s great and all, but what happens when there’s no successful outcome?

In the real world, things often go wrong. Promises don’t resolve as expected. And while promise.then() lets you handle this with a rejection handler, await doesn’t know how to deal with failure, so it throws an exception.

async function whale() {
  // When an awaited promise is rejected, it throws an exception.
  await Promise.reject("Hello, ground!")
}

whale()

Luckily, you can handle exceptions thrown by await the same way that you’d handle any JavaScript exceptions: with try and catch.