Вы уже умеете создавать промисы и обрабатывать их через .then() и async/await. Но знаете ли вы, что стандартная библиотека даёт ещё несколько мощных инструментов? В этой статье разберём приёмы, которые избавят вас от лишних строк кода и головной боли при работе с асинхронностью.

Digital-студия WNDER

Promise.allSettled: когда важно получить все результаты

Классический Promise.all падает, если хотя бы один промис отклонён. Но что, если вы хотите дождаться выполнения всех промисов, независимо от их статуса? Например, вы загружаете данные из нескольких API, и падение одного не должно ломать всю картину.

Вот тут на сцену выходит Promise.allSettled. Он возвращает массив объектов с полями status и value или reason.


const promises = [
  fetch('/api/user'),
  fetch('/api/posts'),
  fetch('/api/comments')
];

const results = await Promise.allSettled(promises);
results.forEach((result, index) => {
  if (result.status === 'fulfilled') {
    console.log(`Запрос ${index} успешен:`, result.value);
  } else {
    console.error(`Запрос ${index} упал:`, result.reason);
  }
});

Этот метод идеален для сбора статистики, где ошибки не критичны, но важны все результаты.

Promise.race и Promise.any: гонки и первый успех

Иногда нужно получить результат самого быстрого промиса. Для этого есть Promise.race. Он возвращает первый завершившийся промис — неважно, успешно или с ошибкой.


const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error('Таймаут')), 5000)
);

const fetchData = fetch('/api/data');

try {
  const result = await Promise.race([fetchData, timeout]);
  console.log('Данные получены:', result);
} catch (err) {
  console.error('Не успели:', err.message);
}

А если вам нужен первый успешный промис, игнорируя ошибки, используйте Promise.any. Он ждёт, пока хотя бы один промис выполнится успешно, и только если все упадут — выбрасывает AggregateError.


const mirrors = [
  fetch('https://mirror1.example.com/data'),
  fetch('https://mirror2.example.com/data'),
  fetch('https://mirror3.example.com/data')
];

try {
  const fastest = await Promise.any(mirrors);
  console.log('Первый успешный ответ:', fastest);
} catch (e) {
  console.error('Все зеркала недоступны', e.errors);
}

Эти методы часто путают. Запомните: race — кто быстрее, any — кто быстрее и успешен.

Правило: Если вам нужно дождаться всех промисов, но не хотите падать из-за одной ошибки — используйте Promise.allSettled вместо Promise.all. Это спасёт ваш код от неожиданных крашей.

Обработка ошибок в async/await: не только try/catch

Многие пишут так:


async function getData() {
  try {
    const res = await fetch('/api/data');
    const data = await res.json();
    return data;
  } catch (err) {
    console.error(err);
  }
}

Это работает, но есть более элегантный паттерн — обёртка, возвращающая кортеж [error, result]. Он избавляет от вложенных try/catch и делает код линейным.


async function safeAwait(promise) {
  try {
    const result = await promise;
    return [null, result];
  } catch (error) {
    return [error, null];
  }
}

// Использование
const [err, data] = await safeAwait(fetch('/api/data').then(r => r.json()));
if (err) {
  console.error('Ошибка:', err);
} else {
  console.log('Данные:', data);
}

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

Отмена промисов: AbortController и не только

Промисы не имеют встроенного метода cancel. Но это не значит, что отмена невозможна. Чаще всего используют AbortController для fetch-запросов.


const controller = new AbortController();
const signal = controller.signal;

fetch('/api/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Запрос отменён');
    } else {
      console.error('Другая ошибка', err);
    }
  });

// Где-то позже
controller.abort();

Для собственных промисов можно реализовать отмену через гонку с промисом-отменой. Но помните: это не отменяет саму операцию, а лишь игнорирует её результат.

Асинхронные итераторы и for await...of

Если вы работаете с потоками данных или постраничной загрузкой, асинхронные итераторы — ваш друг. Они позволяют перебирать асинхронные источники в цикле for await...of.


async function* fetchPages() {
  let page = 1;
  while (true) {
    const res = await fetch(`/api/items?page=${page}`);
    const data = await res.json();
    if (data.length === 0) break;
    yield data;
    page++;
  }
}

for await (const page of fetchPages()) {
  console.log('Страница:', page);
}

Этот приём удобен для обработки больших объёмов данных без загрузки всего сразу.

Сравнение методов Promise

Чтобы вы не запутались, вот таблица с основными методами и их поведением.

Метод Возвращает Когда завершается Обработка ошибок
Promise.all Массив результатов Когда все успешны Первый reject
Promise.allSettled Массив объектов статуса Когда все завершены Никогда не reject
Promise.race Результат первого Первый завершившийся Если первый reject
Promise.any Результат первого успешного Первый успешный AggregateError, если все reject
Лайфхак: Используйте Promise.allSettled для массовых операций, где важна каждая ошибка. А Promise.any — для отказоустойчивости, когда достаточно одного успешного ответа.

Что в итоге

Промисы — это не только .then() и async/await. Комбинируя allSettled, any, race, обёртки для ошибок и асинхронные итераторы, вы пишете более надёжный и читаемый код. Не бойтесь экспериментировать: каждый из этих методов решает конкретную задачу и делает ваш асинхронный код чище.

Помните: главное — понимать, какой инструмент подходит для вашей ситуации. И тогда промисы перестанут быть источником багов, а станут мощным союзником.

Студия WNDER