Execute Program

JavaScript Concurrency: Catching Promise Rejections

Welcome to the Catching Promise Rejections lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • In regular JavaScript, we recover from exceptions with catch.

  • >
    function tryAndCatch() {
    try {
    throw new Error();
    } catch (e) {
    return 'caught';
    }
    }
    tryAndCatch();
    Result:
    'caught'Pass Icon
  • We can catch promise rejection as well, but not with the usual try { ... } catch { ... } syntax. Instead, promises have a .catch(...) method. When a promise rejects, its catch callback (if any) is called.

  • In some ways, catch behaves like then. Both take a callback function, and both return promises containing the callback's return value. The main difference is that then is called under normal conditions, while catch is called only when a promise rejects. With catch, we can recover from a rejection, and return a fulfilled promise rather than a rejected one.

  • >
    Promise.resolve()
    .then(() => {
    throw new Error('this will reject!');
    })
    .catch(() => 'caught');
    Asynchronous Icon Async Result:
  • >
    const users = [];

    function firstUserName() {
    if (users.length === 0) {
    throw new Error('there are no users');
    } else {
    return users[0].name;
    }
    }

    // Return a promise containing the first user's name, or "undefined" if there
    // are no users.
    Promise.resolve()
    .then(() => firstUserName())
    .catch(() => undefined);
    Asynchronous Icon Async Result:
    {fulfilled: undefined}Pass Icon
  • This is an important point that's easy to forget: catch handles rejected promises, but it usually returns a fulfilled promise, as it did above by returning {fulfilled: undefined}!

  • Here's a code problem:

    A user is making a purchase and enters an invalid discount code. The code here looks for the discount code in the "database" (we use an array instead of a real database). If the discount code doesn't exist, we return a rejected promise.

    Unfortunately, the rejected promise will cause our application to return an HTTP 500 error. We don't want that, so we need to handle the rejection. Handle the rejected promise with catch, returning a promise that fulfills with undefined instead.

    const discounts = [
    {code: 'first-month-free'},
    {code: 'annual-sale'},
    ];

    function findDiscountCode(code) {
    const discount = discounts.find(d => d.code === code);
    if (discount === undefined) {
    return Promise.reject(new Error('discount does not exist'));
    } else {
    return Promise.resolve(discount);
    }
    }
    findDiscountCode('please-give-everything-to-me-for-free')
    .catch(error => undefined);
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: undefined}
    Yours:
    {fulfilled: undefined}Pass Icon
  • The catch method is the most common error-handling mechanism in promises. But there's another, less common method as well.

  • So far, our then callbacks have always taken one callback function.

  • >
    Promise.resolve(['one', 'two'])
    .then(value => value.length);
    Asynchronous Icon Async Result:
    {fulfilled: 2}Pass Icon
  • But then actually takes two callback functions as arguments: .then(onFulfilled, onRejected). The onRejected callback is called when the parent promise rejects.

  • >
    Promise.reject(new Error('user does not exist'))
    .then(
    value => 'it fulfilled',
    reason => 'it rejected'
    );
    Asynchronous Icon Async Result:
  • A promise can fulfill or reject, but it can't do both. Only one of the two then callbacks is called: either onFulfilled or onRejected.

  • Finally, a note about the relationship between rejection and errors. In most cases, rejection is used to indicate an error: something genuinely went wrong. But we can use it for other purposes too.

  • For example, imagine that we're building a web-based email product. We want to let users import their email archives from other email providers. We do that by connecting to the other provider's mail servers and downloading all of the user's email. There may be hundreds of thousands of emails, so this can take hours. We want to allow the user to abort the import if needed.

  • We can implement our email import as two chained promises:

    1. Download all of the email from the user's email server, storing it in a temporary location. (This is the slow part!)
    2. then, after all of the email is downloaded, save it in our own email database. (This is much faster than the download.)
  • If the user asks us to abort the import during phase 1, we can reject that promise. That prevents phase 2 (save the emails in our database), so it's as if we never started. We rejected a promise, but that rejection was due to a user's request, not due to an error.