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

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

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

Начать курс

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

Современная веб-разработка невозможна без понимания асинхронного программирования. TypeScript, как надстройка над JavaScript, предоставляет мощные инструменты для работы с асинхронным кодом, делая его более безопасным и предсказуемым. В этом руководстве мы разберем ключевые концепции асинхронного программирования в TypeScript, начиная с основ и заканчивая практическими примерами.

Что такое асинхронное программирование и зачем оно нужно?

В синхронном программировании код выполняется строго последовательно: каждая следующая операция ждет завершения предыдущей. Это создает проблемы при работе с длительными задачами, такими как запросы к серверу, чтение файлов или таймеры. Асинхронное программирование позволяет выполнять такие задачи в фоновом режиме, не блокируя основной поток выполнения.

TypeScript добавляет к асинхронному JavaScript строгую типизацию, что помогает отлавливать ошибки на этапе компиляции. Например, типизированные Promise позволяют точно знать, какой тип данных вернет асинхронная функция.

// Синхронный код - блокирует выполнение
function fetchData(): string {
  // Долгая операция
  return "данные";
}

// Асинхронный код - не блокирует
async function fetchDataAsync(): Promise<string> {
  const response = await fetch('/api/data');
  return response.json();
}

Основы: Callback, Promise и Async/Await

Исторически асинхронность в JavaScript начиналась с callback-функций, которые передавались в качестве аргументов. Однако такой подход приводил к "аду колбэков" — глубоко вложенным структурам, сложным для понимания и отладки.

Promises (промисы) решили эту проблему, предоставив цепочечный синтаксис. Promise — это объект, представляющий результат асинхронной операции, который может быть в одном из трех состояний: pending (ожидание), fulfilled (выполнено) или rejected (отклонено).

// Пример Promise в TypeScript
function loadUser(id: number): Promise<User> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: "Alice" });
      } else {
        reject(new Error("Invalid user ID"));
      }
    }, 1000);
  });
}

// Использование
loadUser(1)
  .then(user => console.log(user))
  .catch(error => console.error(error));

Async/Await — это синтаксический сахар над Promises, который делает асинхронный код похожим на синхронный. Ключевое слово async перед функцией указывает, что она возвращает Promise, а await приостанавливает выполнение до получения результата.

// Async/Await в TypeScript
async function getUserData(): Promise<void> {
  try {
    const user = await loadUser(1);
    console.log(user.name); // "Alice"
  } catch (error) {
    console.error("Ошибка загрузки:", error);
  }
}

getUserData();

Типизация асинхронных функций в TypeScript

Одно из главных преимуществ TypeScript — строгая типизация. Для асинхронных функций важно правильно указывать тип возвращаемого Promise. Если функция возвращает строку, тип будет Promise<string>. Компилятор TypeScript проверит, что вы не пытаетесь использовать результат до его получения.

Рассмотрим пример с типизированным API-запросом:

interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

async function fetchTodo(id: number): Promise<Todo> {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return await response.json() as Todo;
}

// Использование с типизацией
async function displayTodo(): Promise<void> {
  const todo = await fetchTodo(1);
  console.log(todo.title); // TypeScript знает, что это string
}

Обратите внимание на приведение типа через as Todo. Это гарантирует, что объект будет соответствовать интерфейсу Todo. Однако будьте осторожны: если сервер вернет данные другого формата, TypeScript не выдаст ошибку во время выполнения — только во время компиляции.

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

Иногда необходимо выполнить несколько асинхронных операций одновременно. Для этого используются Promise.all(), Promise.race() и Promise.allSettled().

  • Promise.all() — ждет завершения всех промисов. Если один отклоняется, весь промис отклоняется.
  • Promise.race() — возвращает результат первого завершенного промиса (успешного или отклоненного).
  • Promise.allSettled() — ждет завершения всех промисов и возвращает массив с результатами каждого.
// Параллельное выполнение
async function loadMultipleUsers(): Promise<void> {
  try {
    const [user1, user2, user3] = await Promise.all([
      loadUser(1),
      loadUser(2),
      loadUser(3)
    ]);
    console.log("Все пользователи загружены:", user1, user2, user3);
  } catch (error) {
    console.error("Один из запросов упал:", error);
  }
}

// Обработка ошибок с allSettled
async function loadWithPartialFailure(): Promise<void> {
  const results = await Promise.allSettled([
    loadUser(1),
    loadUser(-1), // Этот вызов упадет
    loadUser(3)
  ]);
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Пользователь ${index + 1}:`, result.value);
    } else {
      console.error(`Пользователь ${index + 1} не загружен:`, result.reason);
    }
  });
}

Для обработки ошибок в асинхронном коде используйте конструкцию try...catch. В TypeScript вы можете создавать собственные классы ошибок для более точной диагностики:

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

async function fetchWithErrorHandling(url: string): Promise<any> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new ApiError(response.status, `HTTP ошибка: ${response.statusText}`);
  }
  return response.json();
}

async function safeFetch(): Promise<void> {
  try {
    const data = await fetchWithErrorHandling('/api/data');
    console.log(data);
  } catch (error) {
    if (                

Блоги

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