.catch()Promise rejection is often seen as an implementation concern. But structurally, it reflects a deeper flaw: ignoring error as a possible shape of state. This piece frames promise rejection not just as a runtime inconvenience—but as a system design challenge.
1. Promises are values, not just execution pathways
A promise represents pending data, but its rejection path must be treated as part of its type.
In FP systems, this resembles Either or Result types.
2. Implicit error handling erodes determinism
Without formal modeling, rejected promises break the compositional guarantees of a system.
3. TypeScript has tools—we just rarely use them fully
Use tagged union types, discriminated unions, and custom wrappers to enforce exhaustive handling.
Overuse of .catch() without intent
Shallow try/catch around non-awaited promises
Ignoring rejection in async function calls
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
async function fetchUser(): Promise<Result<User, FetchError>> {
try {
const user = await api.getUser();
return { ok: true, value: user };
} catch (e) {
return { ok: false, error: e };
}
}
This promotes exhaustive downstream handling and clearer control flow.
Model form submission state as Result<T, E>
Avoid silently failing async hooks
Ensure memoized async logic distinguishes resolved vs rejected states
By formally modeling promise rejection, we turn error handling from reactive patchwork into proactive architecture. It’s not just about writing cleaner code—it’s about acknowledging that failure is part of state.
0
6
0