Photo by Yosuke Ota on Unsplash

On this page

For try/catch, sequential await, and promise rejection behavior in JavaScript, see Async/await in JavaScript and Error handling in JavaScript.

TypeScript does not change runtime errors; it helps you model outcomes so callers handle failures explicitly.

Unknown errors in catch

With useUnknownInCatchVariables (recommended in strict setups), catch (e) types e as unknown. Narrow before use:

function isError(value: unknown): value is Error {
return value instanceof Error;
}
async function run() {
try {
await mightFail();
} catch (e: unknown) {
if (isError(e)) {
console.log(e.message);
} else {
console.log(String(e));
}
}
}

Per-step errors without nested try/catch

When one catch cannot tell which await failed, split steps or use a typed wrapper that never throws:

type Outcome<T> = { ok: true; value: T } | { ok: false; error: unknown };
async function toOutcome<T>(p: Promise<T>): Promise<Outcome<T>> {
try {
return { ok: true, value: await p };
} catch (error) {
return { ok: false, error };
}
}
async function runAll() {
const a = await toOutcome(fetchUser());
if (!a.ok) return console.log("user failed", a.error);
const b = await toOutcome(fetchPosts(a.value.id));
if (!b.ok) return console.log("posts failed", b.error);
console.log(b.value);
}

After if (!a.ok), TypeScript narrows a.value in the success branch.

Tuple pattern from Promise.allSettled-style helpers

A small handle helper returns a tuple [result, error] with a discriminant empty slot:

async function handle<T>(
prom: Promise<T>,
): Promise<[T, undefined] | [undefined, unknown]> {
try {
const res = await prom;
return [res, undefined];
} catch (err) {
return [undefined, err];
}
}
async function runAll() {
const [user, errUser] = await handle(fetchUser());
if (errUser !== undefined) {
return console.log("user", errUser);
}
const [posts, errPosts] = await handle(fetchPosts(user.id));
if (errPosts !== undefined) {
return console.log("posts", errPosts);
}
console.log(posts);
}

Annotate generic T on handle when inference from the argument is insufficient.

Typing functions that may throw versus Result types

APIs can be declared to return Promise<T> (throw on failure) or Promise<Outcome<T>> / Result<T, E> (never throw). Pick one style per layer; mixing thrown errors and result objects in the same module confuses both readers and the type checker.

Declare Promise<T> when failures are exceptional. Use Outcome or a shared Result alias when failure is an ordinary branch and you want exhaustiveness checking at call sites.

Search articles

Type a word to search published articles.

Open the search page