# Async and Promises

> Source: https://learn-typescript.org/async-and-promises/
> Part of Learn TypeScript, free to read.

An `async` function always returns a `Promise`, and TypeScript types it for you:

```typescript
async function getUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json() as User;
}

const user = await getUser("1");     // User — await unwraps the Promise
```

The annotation is `Promise<User>` even though the `return` statement produces a `User`. Writing `: User` on an `async` function is an error, and it is a common early mistake.

## Awaiting

```typescript
const user: User = await getUser("1");
const users: User[] = await Promise.all([getUser("1"), getUser("2")]);
```

`Awaited<T>` unwraps a promise type when you need it in a type position:

```typescript
type Fetched = Awaited<ReturnType<typeof getUser>>;   // User
```

## Errors are `unknown`, not `Error`

This surprises people coming from other languages:

```typescript
try {
  await getUser("1");
} catch (err) {
  console.log(err.message);      // error: 'err' is of type 'unknown'
}
```

JavaScript lets you `throw` anything — a string, a number, an object — so TypeScript cannot assume you caught an `Error`. Narrow before use:

```typescript
try {
  await getUser("1");
} catch (err) {
  if (err instanceof Error) {
    console.error(err.message);
  } else {
    console.error("unknown failure", err);
  }
}
```

For your own error types, a discriminated union plus `instanceof` gives you typed handling:

```typescript
class NotFoundError extends Error {
  readonly kind = "not_found";
  constructor(readonly id: string) { super(`not found: ${id}`); }
}

if (err instanceof NotFoundError) {
  console.error(err.id);         // typed
}
```

## Running things concurrently

```typescript
// sequential — three round trips, one after another
const a = await getUser("1");
const b = await getUser("2");

// concurrent — one round trip's worth of waiting
const [a, b] = await Promise.all([getUser("1"), getUser("2")]);
```

`Promise.all` is correctly typed as a tuple, so `a` and `b` keep their individual types even when they differ:

```typescript
const [user, orders] = await Promise.all([getUser("1"), getOrders("1")]);
// user: User, orders: Order[]
```

`Promise.all` rejects as soon as any input rejects. When partial success is acceptable, use `allSettled`, which never rejects:

```typescript
const results = await Promise.allSettled([getUser("1"), getUser("2")]);
for (const r of results) {
  if (r.status === "fulfilled") console.log(r.value.email);
  else console.error(r.reason);
}
```

That return type is a discriminated union, so narrowing on `r.status` gives you `value` or `reason` — the pattern from [union types and narrowing](/union-types-and-narrowing/), built into the standard library.

## The bug that matters most

```typescript
async function save(user: User) {
  db.write(user);          // not awaited
  return { ok: true };
}
```

The function returns before the write happens, the error becomes an unhandled rejection, and in Node an unhandled rejection terminates the process. This is the single most common defect in generated TypeScript and JavaScript.

Turn on the rule that catches it:

```js eslint.config.js
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/await-thenable": "error",
```

:::warn These rules need type information
They only work with the type-checked ESLint configuration (`parserOptions: { projectService: true }`). Without it they silently do nothing — which is why many projects have the rules listed and still ship floating promises. Verify by writing one deliberately and checking that lint fails.
:::

`no-misused-promises` catches the other classic:

```typescript
items.forEach(async (item) => {
  await process(item);        // forEach ignores the returned promise
});
console.log("done");          // prints immediately. nothing is done.
```

Use a `for...of` loop with `await`, or `Promise.all` over `.map`.

## Cancellation

```typescript
const controller = new AbortController();

const res = await fetch(url, { signal: controller.signal });
controller.abort();      // stops it
```

`AbortSignal.timeout(5000)` gives you a signal that fires on its own. Threading a signal through your async functions is what makes them cancellable, and it is worth doing for anything that talks to a network.

## Exercise

```typescript
// Write `loadDashboard(userId)` that:
//   - fetches the user and their orders CONCURRENTLY
//   - returns { user, orders, total } with an explicit Promise<...> return type
//   - catches failures, narrowing the caught value before reading .message
// Assume getUser(id): Promise<User> and getOrders(id): Promise<Order[]>.

// write your code here
```

## Common questions

### Why is my caught error typed `unknown`?

Because JavaScript can throw any value, so TypeScript cannot assume it is an `Error`. Narrow with `instanceof Error` before reading `.message`. You can set `useUnknownInCatchVariables: false` to get the old `any` behaviour, but the check is finding a real gap.

### Should the return type be `User` or `Promise<User>`?

`Promise<User>`. An `async` function always returns a promise, and annotating the unwrapped type is an error. Most of the time you can omit the annotation and let it be inferred.

### `Promise.all` or `allSettled`?

`all` when any failure should abort the whole operation, `allSettled` when you want whatever succeeded. Note that `all` does not cancel the other work on rejection — it just stops waiting for it, so those requests still run and still cost you.
