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):
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.