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
catchcallbacks 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);Async Result:
>
Promise.reject({explanation: 'oh no'}).catch(reason => reason.explanation.length);Async Result:
{fulfilled: 5}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'
The promise
catchworks in the same way. Thethens after thecatchdon't need to know that the rejection happened. Any value returned by thecatchis passed to the nextthen.>
Promise.resolve().then(() => { throw new Error('this will reject!'); }).catch(() => 'oh no').then(message => message.length);Async Result:
{fulfilled: 5}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 thecatchand take different actions depending on what we see.We'll start with a
getUserCatsfunction 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
getUserCatsthrows the "user has zero cats" error, we'll catch it and fulfill withundefined. But if it throws other errors that we don't expect, likeuser 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
catsproperty at all.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const user = {name: 'Amir', cats: ['Ms. Fluff']};getCatsPromise(user);Async Result:
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const user = {name: 'Amir', cats: []};getCatsPromise(user);Async Result:
{fulfilled: undefined} - Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const user = {name: 'Amir'};getCatsPromise(user);Async Result:
{rejected: 'Error: user has no cats array'} 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
catsproperty at all is a bug. If we blindly turned all rejected promises intoundefined, 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 withinstanceof. 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 ofinstanceofand 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
messageproperty. (It's not a JavaScriptErrorinstance; it's just a regular object.) Add acatchto catch the rejection and return the error message.const promise = Promise.reject({message: 'it failed'});promise.catch(reason => reason.message);Async Result:
- Goal:
{fulfilled: 'it failed'}- Yours:
{fulfilled: 'it failed'}
Here's a slight variation on that: instead of fulfilling with the error message, we want to turn the
messageinto a properErrorobject.Here's a code problem:
This code's rejection reason is an object with a
messageproperty. Add acatchto catch the rejection, then re-throw a properErrorthat contains themessage.const promise = Promise.reject({message: 'it failed'});promise.catch(reason => {throw new Error(reason.message);});Async Result:
- Goal:
{rejected: 'Error: it failed'}- Yours:
{rejected: 'Error: it failed'}