Gernar
JavaScript: асинхронность

Как выполнить несколько Promise одновременно

Разбор вопроса «Как выполнить несколько Promise одновременно» для Frontend Developer: что проверяет интервьюер, ключевые тезисы, практические примеры и частые ошибки.

Вопрос

Как выполнить несколько Promise одновременно

Профессия

Frontend Developer

Что хочет услышать интервьюер

Интервьюер хочет убедиться, что кандидат понимает, как работать с несколькими асинхронными операциями одновременно, знает методы Promise.all и Promise.allSettled, а также может объяснить их различия и применение.

Ключевые тезисы

  • Для выполнения нескольких Promise одновременно можно использовать метод Promise.all, который принимает массив промисов и возвращает новый промис.
  • Promise.all разрешается, когда все переданные промисы успешно завершаются, и возвращает массив их результатов.
  • Если хотя бы один из промисов завершится с ошибкой, Promise.all немедленно завершится с этой ошибкой, и остальные промисы будут проигнорированы.
  • Если нужно выполнить промисы параллельно, но получить результаты всех, независимо от ошибок, можно использовать Promise.allSettled.
  • Promise.allSettled возвращает массив объектов с результатами выполнения каждого промиса, включая статус (fulfilled/rejected) и значение/причину ошибки.

Подробный ответ

Для выполнения нескольких Promise одновременно в JavaScript используются методы Promise.all и Promise.allSettled. Promise.all принимает массив промисов и возвращает новый промис, который разрешается, когда все переданные промисы успешно завершаются. Результатом будет массив значений в том же порядке, что и исходные промисы. Если хотя бы один промис завершится с ошибкой, Promise.all немедленно отклонится с этой ошибкой, и остальные промисы будут проигнорированы. Это полезно, когда вам нужны все результаты, и вы не можете продолжить без них. Например, при загрузке нескольких файлов, которые зависят друг от друга.

Promise.allSettled работает иначе: он ждет завершения всех промисов, независимо от их статуса (успех или ошибка). Результатом будет массив объектов, каждый из которых содержит статус промиса (fulfilled или rejected) и его значение или причину ошибки. Это полезно, когда вам важно знать результат всех операций, даже если некоторые из них завершились неудачно. Например, при отправке нескольких независимых запросов к API.

Важно понимать, что оба метода выполняют промисы параллельно, а не последовательно. Если вам нужно ограничить количество одновременно выполняемых промисов, можно использовать библиотеки типа p-limit или реализовать подобную логику самостоятельно с помощью цикла и счетчика.

Практические примеры

Пример 1

Пример использования Promise.all:

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // [1, 2, 3]
  })
  .catch(error => {
    console.error(error);
  });

Пример 2

Пример использования Promise.allSettled:

const promise1 = Promise.resolve('Успех');
const promise2 = Promise.reject('Ошибка');
const promise3 = Promise.resolve('Еще успех');

Promise.allSettled([promise1, promise2, promise3])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('Успешно:', result.value);
      } else {
        console.log('Ошибка:', result.reason);
      }
    });
  });

Пример 3

Пример обработки ошибок в Promise.all с продолжением выполнения:

const promises = [
  fetch('url1').catch(e => ({ error: e, url: 'url1' })),
  fetch('url2').catch(e => ({ error: e, url: 'url2' })),
  fetch('url3').catch(e => ({ error: e, url: 'url3' }))
];

Promise.all(promises)
  .then(responses => {
    responses.forEach(response => {
      if (response.error) {
        console.log('Ошибка при загрузке:', response.url);
      } else {
        console.log('Успешно загружено:', response.url);
      }
    });
  });

Частые ошибки

  • Использование Promise.all для независимых операций, где ошибка в одном промиссе не должна прерывать выполнение остальных. В таких случаях лучше подходит Promise.allSettled.
  • Предположение, что Promise.all выполняет промисы последовательно. На самом деле они запускаются одновременно, а порядок в массиве результатов соответствует порядку в исходном массиве промисов.
  • Необработка ошибок в Promise.all, что приводит к неожиданным падениям приложения.

Связанные темы

  • Цикл событий (Event Loop) в JavaScript
  • Асинхронные функции (async/await)
  • Обработка ошибок в асинхронном коде
  • Паттерны ограничения параллелизма (throttling, debouncing)

Follow-up вопросы

В чем разница между Promise.all и Promise.allSettled?

Уровень: basic

Promise.all завершается с ошибкой, если хотя бы один промис завершился неудачно, и возвращает массив результатов только для успешных промисов. Promise.allSettled возвращает массив объектов с результатами всех промисов, независимо от их статуса.

Что произойдет, если передать пустой массив в Promise.all?

Уровень: basic

Promise.all немедленно завершится успешно и вернет пустой массив, так как нет промисов для выполнения.

Как можно обработать ошибки в Promise.all, чтобы продолжить выполнение остальных промисов?

Уровень: intermediate

Для этого можно использовать Promise.allSettled, который возвращает результаты всех промисов, включая те, которые завершились с ошибкой. После этого можно вручную отфильтровать результаты.

Можно ли использовать Promise.all для выполнения промисов последовательно?

Уровень: intermediate

Нет, Promise.all выполняет промисы параллельно. Для последовательного выполнения можно использовать цепочку then или async/await в цикле.

Как бы ты реализовал ограничение на количество одновременно выполняемых промисов?

Уровень: advanced

Для этого можно использовать библиотеку, например, p-limit, или написать собственную реализацию с помощью счетчика и очереди, которая ограничивает количество одновременно выполняемых промисов.

Содержание