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
anyorunknownwhen catching exceptions. The same is true when catching rejected promises. By default,somePromise.catch(...)gives us a "reason" with the typeany. ("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);Async Result:
The rejection reason's
anytype 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 anumbervariable, but the value at runtime is actually a string.>
Promise.reject('it failed').catch(reason => {const theReason: number = reason;return theReason;});Async Result:
{fulfilled: 'it failed'}When we encounter an
any, there's always some way to restore type safety. Often, the solution is to use theunknowntype instead ofany, 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!");}});Async Result:
{fulfilled: 'it failed'}>
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!");}});Async Result:
type error: Type 'string' is not assignable to type 'number'.
As usual, we have to choose the level of safety that we want. If we want more safety, we use
unknownand a type guard, like above. If we're willing to tolerate an increased chance of bugs, we can useanyand carry on without adding any type guards. Fortunately, usingcatchwith promises is uncommon, just likecatchwith exceptions is, so this is only a mild inconvenience.