Асинхронное программирование в TypeScript: полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в асинхронное программирование на TypeScript

Асинхронное программирование — это подход к написанию кода, при котором операции, требующие времени (например, запросы к серверу, чтение файлов, таймеры), не блокируют выполнение основного потока программы. В современной веб-разработке асинхронность — это не просто удобство, а необходимость. JavaScript и TypeScript, будучи однопоточными языками, используют цикл событий (Event Loop) для обработки асинхронных операций.

TypeScript добавляет к этой мощной парадигме статическую типизацию, что делает код более предсказуемым и безопасным. В этом руководстве мы разберем все аспекты асинхронного программирования: от классических колбэков до современных async/await с полной типизацией.

Основы асинхронности: от колбэков к промисам

Проблема колбэков и «ад колбэков»

Изначально асинхронность в JavaScript реализовывалась через колбэки (функции обратного вызова). Однако такой подход быстро приводил к нечитаемому коду:

function loadData(callback: (data: string) => void) {
    setTimeout(() => {
        callback("Данные загружены");
    }, 1000);
}

loadData((data) => {
    console.log(data);
    // Вложенный вызов
    loadData((data2) => {
        console.log(data2);
    });
});

Такой код сложно отлаживать и поддерживать. TypeScript не решает проблему вложенности, но помогает типизировать параметры колбэков.

Промисы (Promise) — спасение от ада

Промис представляет собой объект, который может находиться в одном из трех состояний: pending (ожидание), fulfilled (выполнено успешно) или rejected (отклонено с ошибкой). TypeScript позволяет строго типизировать тип данных, которые вернет промис:

function fetchUser(id: number): Promise<User> {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (id > 0) {
                resolve({ id, name: "Иван Петров" });
            } else {
                reject(new Error("Некорректный ID"));
            }
        }, 500);
    });
}

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

// Использование
fetchUser(1).then(user => {
    console.log(user.name); // TypeScript знает, что user — это User
}).catch(error => {
    console.error(error.message);
});

Благодаря дженерику Promise<User> компилятор TypeScript гарантирует, что внутри .then() мы получим объект именно этого типа.

Async/Await: элегантная асинхронность

Синтаксис и типизация

Ключевые слова async и await позволяют писать асинхронный код, который выглядит как синхронный. Функция, помеченная async, всегда возвращает промис:

async function getGreeting(name: string): Promise<string> {
    return `Привет, ${name}!`;
}

// Тип возвращаемого значения — Promise<string>
const result: Promise<string> = getGreeting("Мир");

Важно помнить: если async функция возвращает не промис, TypeScript автоматически оборачивает его в промис.

Пример с реальной логикой

Рассмотрим типичный сценарий — получение данных с API и их обработка:

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: ${response.status}`);
    }
    const data: Post[] = await response.json();
    return data;
}

async function displayPosts() {
    try {
        const posts = await fetchPosts(1);
        console.log(`Получено ${posts.length} постов:`);
        posts.forEach(post => console.log(`- ${post.title}`));
    } catch (error) {
        if (error instanceof Error) {
            console.error("Не удалось загрузить посты:", error.message);
        }
    }
}

displayPosts();

Обратите внимание на типизацию: response.json() возвращает Promise<any>, но мы явно указываем Post[], чтобы TypeScript проверил корректность дальнейшего использования данных.

Параллельное выполнение и обработка ошибок

Promise.all, Promise.race и Promise.allSettled

Часто требуется выполнить несколько асинхронных операций параллельно. Для этого существуют статические методы класса Promise. TypeScript корректно выводит типы при их использовании:

async function loadAllUsers(): Promise<User[]> {
    const ids = [1, 2, 3, 4, 5];
    
    // Promise.all возвращает Promise<[User, User, User, User, User]>
    const users = await Promise.all(
        ids.map(id => fetchUser(id))
    );
    
    return users; // TypeScript знает, что это User[]
}

// Promise.race — первый завершенный (успех или ошибка)
async function getFastestResponse(): Promise<string> {
    const promises = [
        fetch("https://api1.example.com").then(r => r.text()),
        fetch("https://api2.example.com").then(r => r.text()),
    ];
    return Promise.race(promises);
}

// Promise.allSettled — дожидается всех, даже если некоторые упали
async function checkAllServices(): Promise<void> {
    const results = await Promise.allSettled([
        fetch("https://service1.com/health"),
        fetch("https://service2.com/health"),
    ]);
    
    results.forEach((result, index) => {
        if (result.status === "fulfilled") {
            console.log(`Сервис ${index + 1}: OK`);
        } else {
            console.error(`Сервис ${index + 1}: ${result.reason}`);
        }
    });
}

Типизированная обработка ошибок

В TypeScript рекомендуется создавать пользовательские классы ошибок для лучшего контроля:

class NetworkError extends Error {
    constructor(
        message: string,
        public statusCode: number
    ) {
        super(message);
        this.name = "NetworkError";
    }
}

async function safeFetch(url: string): Promise<Response> {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new NetworkError(
                `Ошибка запроса к ${url}`,
                response.status
            );
        }
        return response;
    } catch (error) {
        if (error instanceof NetworkError) {
            throw error; // Пробра                

Блоги

Рекомендаций по книгам