Работа Promise.all в Node.js: параллельно или последовательно

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

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

Взглянем на пример с запросами к базе данных:

JS
Скопировать код
let promiseArray = [User.findById(1), User.findById(2), User.findById(3)]; 
Promise.all(promiseArray).then(users => console.log('Пользователи загрузились быстрее, чем вы успеете сказать "осенняя погода"!'));

Каждый вызов 'findById' исполняется независимо от остальных, что позволяет запросам к базе данных происходить одновременно. Однако результат будет выведен в консоль только после получения всех данных.

Кинга Идем в IT: пошаговый план для смены профессии

Цикл For — старый добрый подход

Для последовательного выполнения промисов можно использовать Array.reduce() или async/await в цикле. Пример с использованием reduce():

JS
Скопировать код
let promiseFactory = [fn1, fn2, fn3]; // Функции, возвращающие промисы
promiseFactory.reduce((prevPromise, nextFn) => prevPromise.then(nextFn), Promise.resolve()).then(result => console.log('Выполнено по очереди. Чувствуете дух приключений?'));

В данном контексте промисы выполняются по очереди. Теперь перейдём к использованию async/await:

JS
Скопировать код
async function keepInLine(tasks) {
  for (const task of tasks) {
    await task(); // Каждая задача ожидает своего завершения, прежде чем начнётся следующая.
  }
}

В цикле с использованием await каждая операция выполняется последовательно и структурированно.

Пойми свой Promise

Промисы активируются сразу после создания

Запомните, что новый промис автоматически запускается на выполнение. К моменту передачи его в Promise.all, каждый промис уже находится в процессе выполнения.

Promise.allSettled — терпеливый подход

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

Одновременное против параллельного выполнения

Архитектура Node.js не поддерживает истинную многопоточность из-за своего однопоточного исполнения. Однако Node.js эффективно реализует «одновременное» выполнение задач благодаря циклу событий, который оптимизирует неблокирующие I/O операции.

Глубже в тему

Обработка последовательных операций с общими ресурсами

Когда вам требуется работать с общими ресурсами:

JS
Скопировать код
let sharedResources = {};

async function danceInSequence(tasks, resources) {
  for (const task of tasks) {
    Object.assign(resources, await task(resources)); // Каждая задача имеет возможность обновить общие ресурсы.
  }
}

Таким образом, задачи используют и модифицируют общие ресурсы последовательно.

Искусство создания цепочек промисов

При создании цепочек с использованием .then, важно возвращать новый промис:

JS
Скопировать код
promise.then(result => {
  return new Promise((resolve, reject) => {
    // Здесь происходит ваша асинхронная магия!
  });
});

Если вы забудете видеть вернуть промис, это спровоцирует ошибки и непредсказуемое поведение.

Визуализация

Представьте автобусный вокзал с несколькими автобусами (Promise.all), готовыми отправиться по маршрутам:

Markdown
Скопировать код
Автобус №1 🚌: Доехать за 5 минут
Автобус №2 🚌: Доехать за 10 минут
Автобус №3 🚌: Доехать за 3 минуты

Автобусы отправляются одновременно, но прибывают в разное время:

Markdown
Скопировать код
| Отправление 🚦 | Автобус №1 🚌 | Автобус №2 🚌 | Автобус №3 🚌 |
| :----------: | :-------: | :-------: | :-------: |
|   **0 мин**  | Начало    | Начало    | Начало    |
|   **3 мин**  |           |           | Прибытие  |
|   **5 мин**  | Прибытие  |           |           |
|  **10 мин**  |           | Прибытие  |           |

Группа пассажиров (результат Promise.all) отправится дальше только после прибытия последнего автобуса.

Дополнительные особенности

Использование рекурсии для последовательной работы

Решением для динамического списка промисов может стать рекурсия:

JS
Скопировать код
async function marchOneByOne(index, tasks) {
  if (index >= tasks.length) {
    return; // Все задачи выполнены.
  }
  await tasks[index]();
  await marchOneByOne(index + 1, tasks); // Следующая задача.
}

marchOneByOne(0, promiseFactories)
  .then(() => console.log("Марш окончен. Пора на заслуженный отдых!"));

Симуляция асинхронных операций

Для симуляции setTimeout подходит как асинхронная операция:

JS
Скопировать код
function pretendPromise(result, delay) {
  return new Promise(resolve => setTimeout(() => resolve(result), delay))
  .then(() => console.log("Промис выполнен, можем продолжить!"));
}

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

Относимся к обработке ошибок серьезно

Не забывайте про блок обработки ошибок для обеспечения стабильности ваших асинхронных операций:

JS
Скопировать код
Promise.all(promises)
  .then(results => console.log(results))
  .catch(error => console.error("Обнаружена проблема: ", error));

Это поможет вашим операциям быть надёжными, даже если что-то пойдет не так.

Полезные материалы

  1. Promise.all() – JavaScript | MDNДокументация MDN по Promise.all().
  2. The event loop – JavaScript | MDN — Расширенный обзор цикла событий JavaScript.
  3. JavaScript Promises: an introduction — Вводный материал по промисам JavaScript для глубокого понимания асинхронного программирования.
  4. Tasks, microtasks, queues and schedules – JakeArchibald.com — Изучение задач и микрозадач JavaScript для понимания асинхронного кода.
  5. Node.js — The Node.js Event Loop, Timers, and process.nextTick() — Обсуждаем цикл событий, таймеры и process.nextTick() в контексте Node.js.
  6. Promise API — Руководство с сравнением Promise.all и Promise.allSettled, объясняющее поведение промисов и предлагающее лучшие практики работы с ними.