> Как реализовать функцию any, которая работает как Promise.any? (JavaScript)

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

Компании: Т-Банк, Яндекс

Стек: JavaScript

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

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

Promise.any принимает итерируемый объект промисов и возвращает новый промис, который резолвится первым успешно выполненным промисом. Если все промисы отклонены, возвращается AggregateError со всеми ошибками. Реализация включает отслеживание количества отклонённых промисов и сбор ошибок. В отличие от Promise.race, учитывает только успешные результаты, игнорируя отклонённые до первого resolve.

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

Promise.any - один из комбинаторов промисов, появившийся в ES2021. Его ключевая особенность: он ждёт первый успешный (fulfilled) промис, в отличие от Promise.race, который реагирует на первый завершённый (settled) промис любого статуса. Это делает Promise.any полезным для сценариев, где важен хотя бы один успешный результат из нескольких источников, например, при запросах к нескольким CDN или API-эндпоинтам.

При реализации нужно учесть:

  • Итерируемый объект может быть пустым - тогда сразу возвращается отклонённый промис с AggregateError
  • Если все промисы отклонены, собираем все ошибки в массив и передаём их в AggregateError
  • При первом успешном результате сразу резолвим итоговый промис
  • Необходимо обработать случай, когда передан не-итерируемый объект (выбросить TypeError)

На практике

В реальных проектах Promise.any уже доступен нативно в современных средах (Node.js 15+, Chrome 85+, Firefox 88+). Полифилл может потребоваться для поддержки старых браузеров. Часто используется для:

  • fallback-запросов к разным серверам
  • загрузки ресурсов из нескольких источников
  • реализации стратегий отказоустойчивости

Важно помнить, что Promise.any не отменяет остальные промисы после первого успеха - они продолжают выполняться, но их результаты игнорируются.

Пример кода

JAVASCRIPT
function promiseAny(iterable) {
return new Promise((resolve, reject) => {
if (iterable == null || typeof iterable[Symbol.iterator] !== 'function') {
return reject(new TypeError('Argument is not iterable'));
}
const promises = Array.from(iterable);
if (promises.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
const errors = [];
let rejectedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => resolve(value))
.catch(error => {
errors[index] = error;
rejectedCount++;
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
}

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

Начни с краткого определения Promise.any и его отличия от Promise.race. Затем объясни алгоритм реализации, акцентируя внимание на сборе ошибок и условии для AggregateError. Покажи понимание edge cases: пустой массив, не-итерируемый аргумент, смешанные типы значений. Упомяни, что в реализации нужно использовать Promise.resolve() для обработки non-promise значений. Хорошо бы добавить, что нативная реализация может иметь оптимизации, недоступные в полифилле.

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

  • Понимание работы промисов и их комбинаторов
  • Умение обрабатывать асинхронные сценарии с несколькими промисами
  • Знание специфических ошибок (AggregateError, TypeError)
  • Внимание к edge cases и граничным условиям
  • Способность реализовать стандартный API с нуля

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

  • Использование Promise.race вместо отслеживания всех отклонений
  • Неправильная обработка пустого итерируемого объекта
  • Забывание обернуть non-promise значения через Promise.resolve()
  • Несоблюдение порядка ошибок в AggregateError (соответствие индексам исходного массива)
  • Отсутствие проверки на итерируемость аргумента

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

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