Asynchronous functions

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

Asynchronous functions

Asynchronous functions are functions created with the async keyword. They let you use an await keyword to wait for promises to resolve. This await lets you write asynchronous code as if they were synchronous.

Declaring asynchronous functions

You declare asynchronous functions with an async keyword. The async keyword must be placed before the function keyword.

// Function declaration
async function functionName (arguments) {
  // Do something asynchronous
}

You can also create asynchronous functions with the arrow function syntax. In this case, you write async before the arguments.

const functionName = async (arguments) => {
  // Do something asynchronous
}

Using asynchronous functions

You can call asynchronous functions like normal functions. You write the name of the function, followed by parenthesis.

// Calling an async function
anAsyncFunction()

Asynchronous functions always return a promise

It doesn’t matter what you return. The returned value will always be a promise.

const getOne = async _ => {
  return 1
}

const promise = getOne()
console.log(promise) // Promise
Asynchronous functions always return a promise

This means you can treat asynchronous functions like promises. If you return a value from an asynchronous function, you can use that value in the next then call.

const asyncSum = async (num1, num2) => num1 + num2

asyncSum(5, 10)
  .then(result => console.log(result)) // 15

The await keyword

The await keyword is where the magic happens. It waits for a promise to resolve before doing anything else.

Let’s say we send a Fetch request for a list of my repositories. This is how we write the Fetch request if we use promises.

fetch('https://api.github.com/users/zellwk/repos')
  .then(response => response.json())
  .then(repos => console.log(repos))

We can perform the same Fetch request with asynchronous functions. To do so, we first create an asynchronous function.

We can place the promise we wrote in the asynchronous function. It’ll work as before.

const fetchRepo = async link => {
  return fetch(link)
    .then(response => response.json())
    .then(repos => console.log(repos))
}

fetchRepo('https://api.github.com/users/zellwk/repos')

We know Fetch returns a promise. We can use await to wait for Fetch’s promise to resolve in an asynchronous function. We can declare a variable to hold this resolved value.

const fetchRepo = async link => {
  const response = await fetch(link)
  console.log(response)
}

fetchRepo('https://api.github.com/users/zellwk/repos')

Try logging response into the console. You’ll see that it’s the same response that is returned from a Fetch request.

An image of the response object
"await" waits for the promise to resolve

We need to call response.json to get the repos. Since response.json returns a promise, we can also use await to wait for this promise to resolve.

const fetchRepo = async link => {
  const response = await fetch(link)
  const repos = await response.json()
  console.log(repos)
}

fetchRepo('https://api.github.com/users/zellwk/repos')

If you log repos, you’ll see a list of 30 repositories.

An image of the repos
"await" waits for the promise to resolve once more

Return await

There’s no need to await before returning a promise. You can return the promise directly.

If you return await something, you resolve the original promise first. Then, you create a new promise from the resolved value. So return await effectively does nothing. No need for the extra step. We can return the promise directly.

const fetchRepo = async link => {
  const response = await fetch(link)
  // No need to do this
  return await response.json()
}
const fetchRepo = async link => {
  const response = await fetch(link)
  // Do this instead
  return response.json()
}

Handling errors

await waits for the promise to resolve. It only waits for the then call. This means won’t handle errors in the asynchronous function we wrote.

Let’s see this in action. Say we await for a promise that always throws an error, errorPromise. You’ll see an “uncaught” error in your console.

const errorPromise = _ => {
  return new Promise((resolve, reject) => {
    reject(Error('Something went wrong!'))
  })
}

const asyncFunction = async _ => {
  const res = await errorPromise()
}

asyncFunction()
An uncaught error
An uncaught error

We get this “uncaught” error message because we didn’t catch the error in our code. The console caught it for us, and it tells us that we need to catch the error.

We can use a try/catch block to handle errors in an asynchronous function. A try/catch block looks like this:

try {
  // Try something that may cause an error
} catch (error) {
  // Do something if an error occurs
}

Promises that resolve will always go into the try block. Promises that reject will always go into the catch block.

const asyncFunction = async _ => {
  try {
    const res = errorPromise()
    // Code continues here if successful
  } catch(error) {
    // Code goes here if something goes wrong
    console.log(error)
  }
}
Error was caught and logged
Error was caught and logged

The try block works like this: it executes code from top to bottom, left to right (like normal). When it encounters an error, it skips the rest of the code and goes straight to the catch block.

const asyncFunction = async _ => {
  try {
    const res = await errorPromise()
    console.log('JavaScript will never read this line of code!')
  } catch(error) {
    console.log(error)
  }
}

The promise catcher

Error handling with try/catch works, but it makes the code harder to digest. We don’t want to write try/catch blocks for every asynchronous function we write. It would be horrible!

For this example, imagine that getOne, getTwo and getThree are promises).

const asyncFunction = async () {
  try {
    const one = await getOne(false)
  } catch (error) {
    console.log(error)
  }

  try {
    const two = await getTwo(false)
  } catch (error) {
    console.log(error)
  }

  try {
    const three = await getThree(false)
  } catch (error) {
    console.log(error)
  }
}

There’s a way to make error catching easier.

We know asynchronous functions return promises. This means we can catch errors with a catch call after calling an asynchronous function. With the catch call in place, we no longer need to write try/catch blocks.

const asyncFunction = async _ => {
  const one = await getOne(false)
  const two = await getTwo(false)
  const three = await getThree(false)
}

asyncFunction()
  .catch(e => console.log(e)) // Error: Something went wrong!

Exercise

  1. Use an asynchronous function to fetch a list of your repositories
  2. Handle async errors with the try/catch block
  3. Handle async errors with a Promise catch call