Typed try catch and finally

Handling Errors by Type

Example

finally runs whether or not an error happened: use it to release resources (close connections, hide spinners). Unknown errors are re-thrown, never silently swallowed.

class InsufficientStockError extends Error {
  constructor(public sku: string, public available: number) {
    super(`Only ${available} left for ${sku}`);
    this.name = "InsufficientStockError";
  }
}

const stock: Record<string, number> = { "SKU-1": 2 };

function reserve(sku: string, qty: number): void {
  const available = stock[sku] ?? 0;
  if (qty > available) throw new InsufficientStockError(sku, available);
  stock[sku] = available - qty;
}

function addToCart(sku: string, qty: number): void {
  console.log("lock cart");
  try {
    reserve(sku, qty);
    console.log("added", qty, "x", sku);
  } catch (e) {
    if (e instanceof InsufficientStockError) console.log("Show message:", e.message);
    else throw e;
  } finally {
    console.log("unlock cart");
  }
}

addToCart("SKU-1", 1);
addToCart("SKU-1", 5);

Output

lock cart
added 1 x SKU-1
unlock cart
lock cart
Show message: Only 1 left for SKU-1
unlock cart

Errors are Not Part of the Signature

Note: Unlike Java's throws, TypeScript function types do not say which errors can be thrown. Callers cannot see from reserve(sku, qty): void that it may fail. That is the main reason some teams prefer Result types for expected failures (next page).