How to Use Async/Await in TypeScript

Use async/await in TypeScript when code needs to wait for promises in a readable, synchronous-looking flow. This is perfect for API calls, database requests, file operations, and sequential async workflows.

What you’ll build or solve

You’ll learn how to use async and await in TypeScript with typed promise returns, error handling, and reusable async helpers. You’ll also know how to keep async flows type-safe.

When this approach works best

This approach is the right choice when multiple async steps should stay readable and easy to debug.

Common real-world scenarios include:

  • API requests
  • Database queries
  • File reads
  • Auth token refresh
  • Sequential workflows

This is a bad idea when many async tasks can safely run in parallel.

Prerequisites

You only need:

  • Basic TypeScript functions
  • Familiarity with promises
  • Understanding of try/catch

Step-by-step instructions

Step 1: Create an async function

Use the async keyword before the function.

async function getUser(): Promise<string> {
  return "Alex";
}

Even plain return values become promises automatically.

This makes the return type Promise<string>.

Step 2: Wait for async values with await

Use await inside another async function.

async function showUser(): Promise<void> {
  const user = await getUser();
  console.log(user);
}

This pauses execution until the promise resolves.

The resolved type stays fully inferred.

Step 3: Add error handling

Wrap awaited logic in try/catch.

async function loadProfile(): Promise<void> {
  try {
    const response = await fetch("/api/profile");
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Failed to load profile");
  }
}

This is the cleanest production-ready pattern.

Step 4: Use typed async arrow functions

The same pattern works with arrows.

const getScore = async (): Promise<number> => {
  return 95;
};

What to look for:

  • async wraps the return in a promise
  • await unwraps resolved values
  • Great for readable async flows
  • Use try/catch for failures
  • Keep promise return types explicit

Examples you can copy

API fetch

const response = await fetch("/api/users");

Typed async helper

const loadCount = async (): Promise<number> => 10;

Sequential workflow

await refreshToken();
await loadUser();

Common mistakes and how to fix them

Mistake 1: Using await outside async functions

What the reader might do:

const user = await getUser();

Why it breaks: await requires async context.

Corrected approach:

Wrap it in an async function.

Mistake 2: Forgetting the promise return type

What the reader might do:

async function getUser(): string

Why it breaks: async functions always return promises.

Corrected approach:

Use Promise<string>.

Mistake 3: Serializing independent async work

What the reader might do:

Await many unrelated requests one by one.

Why it breaks: performance slows unnecessarily.

Corrected approach:

Use Promise.all().

Troubleshooting

If await errors, make sure the function is async.

If the return type mismatches, wrap it in Promise<T>.

If performance is slow, parallelize safe tasks.

If failures disappear silently, add try/catch.

Quick recap

  • Use async for promise-returning functions
  • Use await for readable async flow
  • Return Promise<T>
  • Add try/catch for failures
  • Use Promise.all() for parallel safe tasks