Execute Program

JavaScript Concurrency: Recovering From Rejection

Welcome to the Recovering From Rejection lesson!

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

  • Our catch callbacks get the rejection reason as an argument. They can also return values, which will become regular fulfilled promises. As we saw in an earlier lesson, this lets us turn a rejected promise into a fulfilled one.

  • >
    Promise.reject({explanation: 'not enough memory'})
    .catch(reason => reason.explanation);
    Asynchronous Icon Async Result:
  • >
    Promise.reject({explanation: 'oh no'})
    .catch(reason => reason.explanation.length);
    Asynchronous Icon Async Result:
    {fulfilled: 5}Pass Icon
  • When we write a try { ... } catch { ... } in JavaScript, the lines following the try/catch execute as expected. Once the exception is handled, execution can continue normally.

  • >
    function tryAndCatch() {
    try {
    throw new Error('oh no');
    } catch (e) {
    // do nothing
    }
    return 'we got to the end';
    }
    tryAndCatch();
    Result:
    'we got to the end'Pass Icon
  • The promise catch works in the same way. The thens after the catch don't need to know that the rejection happened. Any value returned by the catch is passed to the next then.

  • >
    Promise.resolve()
    .then(() => { throw new Error('this will reject!'); })
    .catch(() => 'oh no')
    .then(message => message.length);
    Asynchronous Icon Async Result:
    {fulfilled: 5}Pass Icon
  • We've already seen what happens when a promise rejects with an error object: Execute Program converts the error into a string. The final result is something like {rejected: 'Error: user does not exist'}.

  • But that's only a convenience so you can type the reasons into our prompts. Inside the JavaScript code, the full reason object is always visible, even if it's an Error.

  • Here's a series of examples where we can reject with multiple different Errors. We'll inspect those rejection reasons inside the catch and take different actions depending on what we see.

  • We'll start with a getUserCats function that doesn't involve any promises. (It's hard to make promises when cats are involved!) This function can throw two different errors.

  • >
    function getUserCats(user) {
    if (user.cats === undefined) {
    throw new Error('user has no cats array');
    } else if (user.cats.length === 0) {
    throw new Error('user has zero cats');
    } else {
    return user.cats;
    }
    }
  • Now we write a higher-level function that uses getUserCats, but returns a promise. This is analogous to making an API request or reading from a database.

  • When getUserCats throws the "user has zero cats" error, we'll catch it and fulfill with undefined. But if it throws other errors that we don't expect, like user has no cats array, we'll pass the error along. That error isn't supposed to happen and indicates a bug, so we want to let it fail.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    function getCatsPromise(user) {
    return Promise.resolve()
    .then(() => getUserCats(user))
    .catch(reason => {
    if (reason.message === 'user has zero cats') {
    return undefined;
    } else {
    /* Re-throw the error, which will cause this promise to reject
    * with the same reason. */
    throw reason;
    }
    });
    }
  • Finally, we call the function with some different users. One has a cat, one has no cats, and one contains a bug: there's no cats property at all.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const user = {name: 'Amir', cats: ['Ms. Fluff']};
    getCatsPromise(user);
    Asynchronous Icon Async Result:
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const user = {name: 'Amir', cats: []};
    getCatsPromise(user);
    Asynchronous Icon Async Result:
    {fulfilled: undefined}Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const user = {name: 'Amir'};
    getCatsPromise(user);
    Asynchronous Icon Async Result:
    {rejected: 'Error: user has no cats array'}Pass Icon
  • For our application, it's important to handle these two error cases differently. An empty cats array is an expected case, but a user with no cats property at all is a bug. If we blindly turned all rejected promises into undefined, we would miss this bug. The general rule is: handle the most specific type of error that you expect, and re-throw any other errors that you see.

  • There's one more way to improve the code above. Currently, we identify errors by inspecting the error message directly, like if (reason.message === ...). Our code would be better if we defined our own error class, then checked for it with instanceof. Then we wouldn't have to rely on the error message string (which might change) to tell us the type of error! Although the details of instanceof and classes are out of scope for this course, consider using them when handling rejected promises.

  • Here's a code problem:

    This code's rejection reason is an object with a message property. (It's not a JavaScript Error instance; it's just a regular object.) Add a catch to catch the rejection and return the error message.

    const promise = Promise.reject({message: 'it failed'});
    promise.catch(reason => reason.message);
    Asynchronous Icon Async Result:
    Goal:
    {fulfilled: 'it failed'}
    Yours:
    {fulfilled: 'it failed'}Pass Icon
  • Here's a slight variation on that: instead of fulfilling with the error message, we want to turn the message into a proper Error object.

  • Here's a code problem:

    This code's rejection reason is an object with a message property. Add a catch to catch the rejection, then re-throw a proper Error that contains the message.

    const promise = Promise.reject({message: 'it failed'});
    promise.catch(reason => {
    throw new Error(reason.message);
    });
    Asynchronous Icon Async Result:
    Goal:
    {rejected: 'Error: it failed'}
    Yours:
    {rejected: 'Error: it failed'}Pass Icon