Asynchronous Programming in TypeScript: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Asynchronous Programming in TypeScript

Asynchronous programming is an approach to writing code where operations that take time (such as server requests, file reading, timers) do not block the execution of the main program thread. In modern web development, asynchronicity is not just a convenience but a necessity. JavaScript and TypeScript, being single-threaded languages, use the Event Loop to handle asynchronous operations.

TypeScript adds static typing to this powerful paradigm, making code more predictable and safer. In this guide, we will cover all aspects of asynchronous programming: from classic callbacks to modern async/await with full typing.



Fundamentals of Asynchronicity: From Callbacks to Promises



The Callback Problem and "Callback Hell"

Initially, asynchronicity in JavaScript was implemented through callbacks. However, this approach quickly led to unreadable code:

function loadData(callback: (data: string) => void) {    setTimeout(() => {        callback("Data loaded");    }, 1000);}

loadData((data) => { console.log(data); // Nested call loadData((data2) => { console.log(data2); });});

Such code is difficult to debug and maintain. TypeScript does not solve the nesting problem, but it helps to type the callback parameters.



Promises — Salvation from Hell

A Promise is an object that can be in one of three states: pending, fulfilled, or rejected. TypeScript allows you to strictly type the data that the promise will return:

function fetchUser(id: number): Promise<User> {    return new Promise((resolve, reject) => {        setTimeout(() => {            if (id > 0) {                resolve({ id, name: "Ivan Petrov" });            } else {                reject(new Error("Invalid ID"));            }        }, 500);    });}

interface User { id: number; name: string;}

// UsagefetchUser(1).then(user => { console.log(user.name); // TypeScript knows that user is a User}).catch(error => { console.error(error.message);});

Thanks to the generic Promise<User>, the TypeScript compiler guarantees that inside .then() we will receive an object of exactly this type.



Async/Await: Elegant Asynchronicity



Syntax and Typing

The async and await keywords allow you to write asynchronous code that looks synchronous. A function marked with async always returns a promise:

async function getGreeting(name: string): Promise<string> {    return `Hello, ${name}!`;}

// Return type is Promise<string>const result: Promise<string> = getGreeting("World");

It is important to remember: if an async function returns something that is not a promise, TypeScript automatically wraps it in a promise.



Example with Real Logic

Let's consider a typical scenario — fetching data from an API and processing it:

interface Post {    userId: number;    id: number;    title: string;    body: string;}

async function fetchPosts(userId: number): Promise<Post[]> { const response = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } const data: Post[] = await response.json(); return data;}

async function displayPosts() { try { const posts = await fetchPosts(1); console.log(`Received ${posts.length} posts:`); posts.forEach(post => console.log(`- ${post.title}`));

Blogs

Book Recommendations