Execute Program

Everyday TypeScript: Rejected Promises

Welcome to the Rejected Promises lesson!

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

  • We've seen that we can only use the types any or unknown when catching exceptions. The same is true when catching rejected promises. By default, somePromise.catch(...) gives us a "reason" with the type any. ("Reason" is the word for the error or other object held inside a rejected promise.)

  • Remember that Execute Program represents fulfilled promises as objects like {fulfilled: theValue}.

  • The purpose of .catch(...) is to handle errors. If our catch callback returns successfully, we get a fulfilled promise back. The promise contains our callback's return value.

  • >
    Promise.reject('it failed')
    /* This `catch` works, but it's unsafe. The `reason` argument
    * implicitly has the type `any`, so we lose type safety! */
    .catch(reason => 'caught: ' + reason);
    Asynchronous Icon Async Result:
  • The rejection reason's any type sacrifices all type safety, as usual. In the example below, we see the classic problem when type safety is broken. We assign the reason to a number variable, but the value at runtime is actually a string.

  • >
    Promise.reject('it failed')
    .catch(reason => {
    const theReason: number = reason;
    return theReason;
    });
    Asynchronous Icon Async Result:
    {fulfilled: 'it failed'}Pass Icon
  • When we encounter an any, there's always some way to restore type safety. Often, the solution is to use the unknown type instead of any, then use type guards to narrow the type safely. That works well in this case.

  • >
    Promise.reject('it failed')
    .catch((reason: unknown) => {
    /* We can only catch rejections as `unknown` or `any`. We use
    * `unknown` because it's safer, but then we have to narrow the type
    * with a conditional. */
    if (typeof reason === 'string') {
    const theReason: string = reason;
    return theReason;
    } else {
    throw new Error("We can't handle that type of reason!");
    }
    });
    Asynchronous Icon Async Result:
    {fulfilled: 'it failed'}Pass Icon
  • >
    Promise.reject('it failed')
    .catch((reason: unknown) => {
    if (typeof reason === 'string') {
    const theReason: number = reason;
    return theReason;
    } else {
    throw new Error("We can't handle that type of reason!");
    }
    });
    Asynchronous Icon Async Result:
    type error: Type 'string' is not assignable to type 'number'.Pass Icon
  • As usual, we have to choose the level of safety that we want. If we want more safety, we use unknown and a type guard, like above. If we're willing to tolerate an increased chance of bugs, we can use any and carry on without adding any type guards. Fortunately, using catch with promises is uncommon, just like catch with exceptions is, so this is only a mild inconvenience.