try / catch

For as simple as async functions appear, they are not without errors. We need to know how to handle them.

When you're first getting started with async/await, it is tempting to use try/catch around every async operation. That's because if you await on a promise that rejects, JavaScript throws a catchable error.

async function runAsync() {
  try {
    await Promise.reject(Error("Oops!"));
  } catch (error) {
    console.error(error.message); // "Oops!"
  }
}

run();

And using a real-world example, let’s make a request to get a GitHub user that clearly doesn’t exist and throw an error when it isn’t successful:

async function runAsync() {
  try {
    const response = await fetch(
      "https://api.github.com/users/lalskjdfalksjdf"
    );
    if (!response.ok) {
      throw new Error("Oops!");
    }
  } catch (error) {
    console.error(error);
  }
}

try / catch is a very helpful way for handling errors in async code because it useful for handling code with synchronous errors as well that are not caught until run time, when the code is executed:

async function runAsync() {
  try {
    await Promise.resolve("hello world");
    null.someProperty;
  } catch (error) {
    console.error(error.message);
  }
}

runAsync();

So all you need to do is wrap all your logic in a try/catch, right? You will, most of the time. However, only be aware that only the await keyword converts rejected promises to catchable errors. Take for example the following code:

async function runAsync() {
  try {
    return Promise.reject(Error("Oops"));
  } catch (error) {
    console.log("won't run");
  }
}

runAsync();

This can simply be fixed by adding an await before the return. Or you can use another, maybe more reliable pattern for catching errors with async / await. Which is to chain on catch like we did in the previous promise syntax:

.catch() pattern

async function getGithubUser() {
  const response = await fetch("https://api.github.com/users/lalskjdfalksjdf");
  if (!response.ok) {
    throw new Error("Oops!");
  }
}

getGithubUser().catch((err) => console.error(err));

And within the catch, you may want to pass a reference to a new function that takes care of handling errors:

function handleError(error) {
  console.error("Error getting user!", error);
}

Importance of catching errors

Why is not catching a promise a problem? Because in many cases, when a promise doesn’t operate as expected, we tell the user about it so they can hopefully to fix it if possible, instead of just logging the error to the console.

For example, if we can’t fetch a user, we may want to tell our user that it wasn’t successful. Maybe the error fetching was a problem with their network connection. So let’s tell them that in a modal. We could use an alert function to display to our users, however, alert doesn't work perfectly in the Scrimba interface, so we'll just use a console.log to simulate:

async function getGithubUser() {
  const response = await fetch('https://api.github.com/users/lalskjdfalksjdf')
  if (!response.ok) {
	throw new Error('Oops!')
  }
}

getGithubUser().catch(handleError);

function handleError(error) {
  console.log(‘Could not fetch user, try resetting your connection’);
  console.error('Error getting user!', error);
}