Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Typing Async/Await in TypeScript

Typing Async/Await

~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Real applications often load data from EXTERNAL sources (database, API, file) – asynchronously. This chapter shows how Promise<T> and async/await interact with TypeScript's type system, simulated with an asynchronous library data store.

Promise<T>: the type for future values

function loadAllBooks(): Promise<Book[]> {
  return new Promise((resolve) => {
    setTimeout(() => resolve([/* ... */]), 100);
  });
}

// The return type Promise<Book[]> says: "returns a Book[] EVENTUALLY, not immediately"

Promise<T> is itself a GENERIC type (chapter 17) – T is the type of the value that will EVENTUALLY be available once the promise successfully completes ("resolves").

async/await: readable syntax for promises

async function loadAndShowBooks(): Promise<void> {
  const books = await loadAllBooks(); // books has type Book[], NOT Promise<Book[]>
  console.log(`${books.length} books loaded.`);
}

A function marked async ALWAYS returns a Promise (even if you don't write it out explicitly – TypeScript automatically "wraps" the given return type). await "unwraps" a promise again – await loadAllBooks() yields Book[], not Promise<Book[]>.

In practice: an asynchronous repository

Let's build a simulated, asynchronous data store – more realistic than synchronous in-memory arrays, but without setting up a real database (beyond this tutorial's language focus):

src/repository/AsyncBookRepository.ts
import { Book } from '../models/Book.js';
import { MediumNotFoundError } from '../errors/LibraryError.js';

// Simulates network/database latency - in a real app this would be an
// actual fetch() or database call
function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export class AsyncBookRepository {
  private books: Book[] = [];

  async all(): Promise<readonly Book[]> {
    await delay(50);
    return this.books;
  }

  async findByIsbn(isbn: string): Promise<Book> {
    await delay(50);
    const book = this.books.find((b) => b.isbn === isbn);
    if (!book) {
      throw new MediumNotFoundError(isbn);
    }
    return book;
  }

  async add(book: Book): Promise<void> {
    await delay(50);
    this.books.push(book);
  }
}

Error handling with async: try/catch still applies

async function showBookDetails(isbn: string): Promise<void> {
  try {
    const book = await repository.findByIsbn(isbn);
    console.log(book.title);
  } catch (error) {
    if (error instanceof MediumNotFoundError) { // EXACTLY like chapter 25
      console.log(`Not found: ${error.isbn}`);
    }
  }
}

await makes a rejected promise behave like a normally THROWN error – try/catch from chapter 25 works EXACTLY the same as with synchronous code, NO separate error-handling API needed for async.

Promise.all with typed results

async function loadMultipleBooks(isbns: string[]): Promise<Book[]> {
  const promises = isbns.map((isbn) => repository.findByIsbn(isbn));
  return Promise.all(promises); // Promise<Book[]> - TypeScript infers this AUTOMATICALLY
}

Promise.all is ITSELF generically typed: from an array of Promise<Book> values, TypeScript automatically infers Promise<Book[]> as the return type – NO manual type annotation needed, pure type inference (chapter 5) applied to a more advanced scenario.

Bonus: top-level await in src/index.ts

// In index.ts you can use 'await' DIRECTLY, with no wrapping async function -
// a modern ES module feature (see chapter 2's "type: module"):
const repository = new AsyncBookRepository();
await repository.add(/* ... */);
const allBooks = await repository.all();
console.log(`${allBooks.length} books in the repository.`);

Tipp: Rule of thumb: EVERY function that uses await internally MUST be async itself (with ONE exception: top-level await in ES modules, as shown above). TypeScript checks this rule automatically – forget async, and the compiler immediately flags an error where await is used.