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.