> Как создать промис, который возвращает результат первого успешно завершившегося промиса в JavaScript (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Яндекс

Стек: JavaScript

> Пример ответа

Короткий ответ

Для создания промиса, возвращающего результат первого успешно завершившегося промиса, используйте Promise.any(). Он принимает итерируемую коллекцию промисов и разрешается первым выполненным (fulfilled). Если все промисы отклонены, Promise.any() отклоняется с AggregateError. Альтернатива - Promise.race() с дополнительной логикой, но Promise.any() - стандартное и предпочтительное решение.

Подробное объяснение

Promise.any() - это статический метод, добавленный в ES2021. Он работает следующим образом:

  • Принимает итерируемый объект (например, массив) промисов.
  • Возвращает новый промис, который разрешается значением первого промиса, перешедшего в состояние fulfilled.
  • Если все промисы отклонены (rejected), возвращаемый промис отклоняется с AggregateError, содержащим все причины отклонений.

Отличие от Promise.race(): Promise.race() разрешается первым завершившимся промисом (независимо от результата - успех или ошибка). Promise.any() игнорирует отклонённые промисы и ждёт первый успешный.

Для имитации Promise.any() в старых средах можно реализовать вручную: создать массив для ошибок, счётчик отклонений и разрешать промис при первом успехе.

На практике

Promise.any() полезен в сценариях, где нужен быстрый успешный ответ от нескольких источников:

  • Загрузка ресурсов из нескольких CDN: берём первый успешно загруженный.
  • Запросы к нескольким API-эндпоинтам с одинаковыми данными (redundancy).
  • Выбор самого быстрого сервера из пула.

Важно помнить: если все промисы отклонены, обработка ошибки через catch обязательна, иначе возникнет необработанное отклонение.

Пример кода

JAVASCRIPT
// Базовое использование Promise.any()
const promise1 = new Promise((resolve, reject) =>
setTimeout(reject, 100, 'Ошибка 1'));
const promise2 = new Promise((resolve, reject) =>
setTimeout(resolve, 200, 'Успех 2'));
const promise3 = new Promise((resolve, reject) =>
setTimeout(resolve, 300, 'Успех 3'));
Promise.any([promise1, promise2, promise3])
.then(result => console.log(result)) // 'Успех 2'
.catch(error => console.error(error));
// Обработка случая, когда все промисы отклонены
const allRejected = [
Promise.reject('Ошибка A'),
Promise.reject('Ошибка B')
];
Promise.any(allRejected)
.catch(error => {
console.log(error instanceof AggregateError); // true
console.log(error.errors); // ['Ошибка A', 'Ошибка B']
});
// Ручная реализация для старых сред (без Promise.any)
function promiseAny(promises) {
return new Promise((resolve, reject) => {
const errors = [];
let rejectedCount = 0;
const total = promises.length;
if (total === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(resolve)
.catch(error => {
errors[index] = error;
rejectedCount++;
if (rejectedCount === total) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
}

Как отвечать на собеседовании

Начните с прямого ответа: используйте Promise.any(). Затем объясните разницу с Promise.race() и Promise.allSettled(). Приведите пример из практики, где Promise.any() решает реальную задачу (например, fallback для CDN). Упомяните обработку AggregateError и совместимость (ES2021). Если спросят про альтернативы, покажите ручную реализацию. Не углубляйтесь в микродетали event loop, если не спросят.

Что проверяет интервьюер

  • Знание современных возможностей JavaScript (ES2021+).
  • Понимание различий между методами Promise (all, race, any, allSettled).
  • Умение обрабатывать краевые случаи (пустой массив, все промисы отклонены).
  • Практический опыт: когда и зачем использовать Promise.any().
  • Способность реализовать аналог вручную (проверка глубокого понимания).

Типичные ошибки

  • Путают Promise.any() с Promise.race() - не учитывают, что race реагирует на первый отклонённый промис.
  • Забывают обработать случай, когда все промисы отклонены (нет catch).
  • Не учитывают, что Promise.any() с пустым массивом сразу отклоняется с AggregateError.
  • Пытаются использовать Promise.all() или Promise.allSettled() для этой задачи.
  • Не знают про AggregateError и его свойство errors.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью