Как выполнить все асинхронные запросы параллельно в Node.js?

Ответ

В Node.js для параллельного выполнения независимых асинхронных операций я использую Promise.all(). Это позволяет запустить все промисы одновременно и дождаться их завершения.

Базовый пример с HTTP-запросами:

const axios = require('axios');

async function fetchMultipleAPIs(urls) {
  const promises = urls.map(url => axios.get(url));

  try {
    const responses = await Promise.all(promises);
    return responses.map(res => res.data);
  } catch (error) {
    // Если один запрос падает, падает весь Promise.all
    console.error('One of the requests failed:', error);
    throw error;
  }
}

// Использование
const apiEndpoints = [
  'https://api.example.com/users',
  'https://api.example.com/posts',
  'https://api.example.com/comments'
];

const data = await fetchMultipleAPIs(apiEndpoints);
console.log('All data fetched:', data);

Когда нужна обработка ошибок без прерывания:

async function fetchWithSettled(urls) {
  const promises = urls.map(async (url) => {
    try {
      const response = await axios.get(url);
      return { status: 'fulfilled', value: response.data };
    } catch (error) {
      return { status: 'rejected', reason: error.message };
    }
  });

  return await Promise.all(promises);
}

// Или используем встроенный Promise.allSettled (Node.js 12.9.0+)
const results = await Promise.allSettled(promises);
const successful = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value);

Важные моменты из моего опыта:

  • Promise.all() оптимален для I/O операций (запросы к БД, API, файлы)
  • Для CPU-intensive задач лучше использовать worker threads
  • Всегда ограничиваю количество параллельных запросов к внешним API (например, с помощью p-limit)
  • При работе с базой данных иногда эффективнее использовать один сложный запрос вместо нескольких параллельных

Ответ 18+ 🔞

Слушай, а вот реально полезная штука, которую я в работе постоянно юзаю — Promise.all(). Ну ты представляешь, сколько времени можно сэкономить, когда тебе надо кучу асинхронных операций запустить параллельно, а не ждать, пока каждая предыдущая ебется как последняя? Это просто волшебство, ёпта.

Вот смотри, простой пример с запросами по API:

const axios = require('axios');

async function fetchMultipleAPIs(urls) {
  const promises = urls.map(url => axios.get(url));

  try {
    const responses = await Promise.all(promises);
    return responses.map(res => res.data);
  } catch (error) {
    // Если один запрос падает, падает весь Promise.all
    console.error('One of the requests failed:', error);
    throw error;
  }
}

// Использование
const apiEndpoints = [
  'https://api.example.com/users',
  'https://api.example.com/posts',
  'https://api.example.com/comments'
];

const data = await fetchMultipleAPIs(apiEndpoints);
console.log('All data fetched:', data);

Но тут есть один нюанс, который многих накрывает медным тазом: если хоть один промис вылетает с ошибкой — весь Promise.all() летит в пизду. Представь, ты делаешь пять запросов, четыре уже отработали, а пятый — херак — 500 ошибка. И всё, приехали, никаких данных не получишь, хотя четыре-то уже готовы. Обидно, блядь.

Поэтому иногда надо обрабатывать ошибки аккуратнее, чтобы не прерывать всё к хуям:

async function fetchWithSettled(urls) {
  const promises = urls.map(async (url) => {
    try {
      const response = await axios.get(url);
      return { status: 'fulfilled', value: response.data };
    } catch (error) {
      return { status: 'rejected', reason: error.message };
    }
  });

  return await Promise.all(promises);
}

// Или используем встроенный Promise.allSettled (Node.js 12.9.0+)
const results = await Promise.allSettled(promises);
const successful = results
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value);

Вот Promise.allSettled() — это вообще песня, он ждёт ВСЕ промисы, неважно, упали они или нет, и потом отдаёт массив с результатами. Удобно, когда тебе важно получить хоть что-то, а не нихуя.

А теперь про важные моменты, которые я на своей шкуре прочувствовал:

  • Promise.all() — это овердохуища для I/O операций. Запросы к API, чтение файлов, обращения к базе — всё это он распараллеливает на ура. Скорость вырастает в разы, особенно когда операции независимые.

  • Но если у тебя CPU-intensive задачи (типа сложных вычислений, шифрования, обработки изображений), то тут Promise.all() не поможет — Node.js всё равно будет выполнять их в одном потоке, по очереди. Для такого дела уже worker threads нужны, иначе производительность будет пиздец какая низкая.

  • Всегда ограничивай количество параллельных запросов к внешним API, а то наткнёшься на лимиты или тебя забанит сервис за DDoS. Я обычно p-limit ставлю, чтобы не больше 5-10 запросов одновременно летело. Иначе можно получить бан, а потом сидеть и думать, какого хуя ничего не работает.

  • И ещё момент с базами данных: иногда один сложный JOIN-запрос будет быстрее, чем десять параллельных простых запросов через Promise.all(). База — она тоже не железная, у неё свои ограничения. Так что думай головой, а не тупо всё параллель запускай.

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