Promise.any() — это статический метод Promise, который принимает массив промисов и возвращает новый промис, который разрешается со значением первого успешно выполненного промиса из массива. Если все промисы отклоняются, то возвращается отклоненный промис с AggregateError, содержащей все ошибки. Полезен для реализации резервных источников данных, параллельных запросов и улучшения производительности.
Основные сценарии использования:
“Первый успешный результат!” ✅
const promise1 = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Ошибка 1')), 500)
);
const promise2 = new Promise(resolve =>
setTimeout(() => resolve('Успех 2'), 200)
);
const promise3 = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Ошибка 3')), 300)
);
Promise.any([promise1, promise2, promise3])
.then(result => console.log('Победитель:', result)) // "Победитель: Успех 2"
.catch(error => console.error('Все провалились:', error));Promise.any() — это мощный инструмент в асинхронном программировании JavaScript, который позволяет выполнять несколько промисов параллельно и работать с первым успешно выполненным результатом. В отличие от Promise.race(), который реагирует на первый завершенный промис (успешный или нет), Promise.any() ждет первый успешный результат.
Promise.any() принимает итерируемый объект (обычно массив) промисов и возвращает новый промис:
const promise1 = new Promise((_, reject) => setTimeout(() => reject(new Error('Ошибка 1')), 1000));
const promise2 = new Promise((_, reject) => setTimeout(() => reject(new Error('Ошибка 2')), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve('Успех 3'), 500));
Promise.any([promise1, promise2, promise3])
.then(result => console.log(result)); // "Успех 3" (первый успешный)Один из наиболее распространенных случаев использования Promise.any() — получение данных из первого доступного источника:
function fetchFromMultipleSources(url) {
const sources = [
fetch(`https://fast-cdn.com${url}`),
fetch(`https://backup-server.com${url}`),
fetch(`https://mirror-site.com${url}`)
];
return Promise.any(sources);
}
// Использование
fetchFromMultipleSources('/api/user-data')
.then(response => response.json())
.then(data => console.log('Данные получены из первого доступного источника:', data))
.catch(error => {
if (error instanceof AggregateError) {
console.error('Все источники недоступны:', error.errors);
} else {
console.error('Ошибка:', error);
}
});Полезно при наличии нескольких источников одних и тех же данных:
function fetchUserData(userId) {
const requests = [
fetch(`https://api1.example.com/users/${userId}`),
fetch(`https://api2.example.com/users/${userId}`),
fetch(`https://api3.example.com/users/${userId}`)
];
return Promise.any(requests)
.then(response => {
if (!response.ok) throw new Error('Ошибка сети');
return response.json();
});
}
// Использование
fetchUserData(123)
.then(userData => {
console.log('Данные пользователя из самого быстрого API:', userData);
})
.catch(error => {
if (error instanceof AggregateError) {
console.error('Все API недоступны:', error.errors);
}
});Можно использовать для проверки доступности первого работающего сервиса:
function checkServiceAvailability() {
const services = [
fetch('https://service1.example.com/health'),
fetch('https://service2.example.com/health'),
fetch('https://service3.example.com/health')
];
return Promise.any(services)
.then(response => {
if (response.ok) {
return 'Сервис доступен';
} else {
throw new Error('Сервис не отвечает');
}
});
}
// Использование
checkServiceAvailability()
.then(status => console.log(status))
.catch(error => {
if (error instanceof AggregateError) {
console.log('Все сервисы недоступны');
}
});function fetchWithFallback(url, fallbackUrls, timeoutMs = 5000) {
const mainRequest = fetch(url);
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Таймаут')), timeoutMs);
});
const fallbackRequests = fallbackUrls.map(fbUrl => fetch(fbUrl));
// Сначала ждем основной запрос или таймаут
return Promise.race([mainRequest, timeout])
.catch(() => {
// Если основной запрос не удался, пробуем резервные
return Promise.any(fallbackRequests);
});
}
// Использование
fetchWithFallback(
'/api/data',
['/api/data-backup1', '/api/data-backup2']
)
.then(response => response.json())
.then(data => console.log('Данные:', data))
.catch(error => {
if (error instanceof AggregateError) {
console.error('Все источники недоступны:', error.errors);
} else {
console.error('Ошибка:', error.message);
}
});function fetchWithCachePreference(url) {
const cacheRequest = getCachedData(url).then(data => {
if (data) return data;
throw new Error('Нет данных в кэше');
});
const networkRequest = fetch(url).then(response => response.json());
return Promise.any([cacheRequest, networkRequest])
.catch(error => {
if (error instanceof AggregateError) {
// Если ни кэш, ни сеть не работают, пробуем еще раз с сетью
return networkRequest;
}
throw error;
});
}
// Использование
fetchWithCachePreference('/api/user-profile')
.then(data => {
console.log('Данные получены (первый доступный источник):', data);
return data;
})
.catch(error => {
if (error instanceof AggregateError) {
console.error('Все источники недоступны:', error.errors);
}
});function fetchFromFastestWorkingAPI(endpoint) {
const apis = [
fetch(`https://api1.example.com${endpoint}`),
fetch(`https://api2.example.com${endpoint}`),
fetch(`https://api3.example.com${endpoint}`)
];
return Promise.any(apis)
.then(response => {
if (!response.ok) throw new Error('Ошибка API');
return response.json();
});
}
// Использование
fetchFromFastestWorkingAPI('/users/123')
.then(userData => {
console.log('Данные пользователя из первого работающего API:', userData);
})
.catch(error => {
if (error instanceof AggregateError) {
console.error('Все API недоступны:', error.errors);
}
});Если все промисы отклоняются, Promise.any() также отклоняется с AggregateError:
const errorPromise1 = new Promise((_, reject) => setTimeout(() => reject(new Error('Ошибка 1')), 2000));
const errorPromise2 = new Promise((_, reject) => setTimeout(() => reject(new Error('Ошибка 2')), 1000));
Promise.any([errorPromise1, errorPromise2])
.then(result => console.log('Результат:', result))
.catch(error => {
if (error instanceof AggregateError) {
console.error('Все промисы провалились:', error.errors);
} else {
console.error('Ошибка:', error.message);
}
});Если передать пустой массив, Promise.any() отклоняется с AggregateError:
// ❌ Будет отклонен
Promise.any([])
.then(result => console.log(result))
.catch(error => {
if (error instanceof AggregateError) {
console.error('Пустой массив промисов');
}
});
// ✅ Проверка на пустой массив
function safeAny(promises) {
if (promises.length === 0) {
return Promise.reject(new Error('Нет промисов для обработки'));
}
return Promise.any(promises);
}Promise.any() преобразует не-промис значения в разрешенные промисы:
Promise.any([42, Promise.reject('Ошибка'), new Promise(resolve => setTimeout(() => resolve(true), 1000))])
.then(result => console.log(result)); // 42 (число мгновенно преобразовано в разрешенный промис)// ❌ Может пропустить ошибки
Promise.any([fetch('/api/data1'), fetch('/api/data2')])
.then(data => console.log(data));
// ✅ Правильная обработка
Promise.any([fetch('/api/data1'), fetch('/api/data2')])
.then(response => {
if (!response.ok) throw new Error('Ошибка сети');
return response.json();
})
.then(data => console.log(data))
.catch(error => {
if (error instanceof AggregateError) {
console.error('Все запросы провалились:', error.errors);
} else {
console.error('Ошибка:', error);
}
});// ❌ Оставшиеся промисы продолжают выполняться
const promises = [slowOperation(), fastOperation()];
Promise.any(promises)
.then(result => {
console.log('Быстрый результат:', result);
// slowOperation() все еще выполняется в фоне
});
// ✅ Отмена оставшихся операций (с использованием AbortController)
const controller = new AbortController();
const signal = controller.signal;
const promises = [
fetch('/api/slow', { signal }),
fetch('/api/fast', { signal })
];
Promise.any(promises)
.then(response => {
controller.abort(); // Отменяем другие запросы
return response.json();
})
.then(data => console.log(data));Promise.any() — это мощный инструмент для оптимизации асинхронных операций, позволяющий выбрать первый доступный успешный результат из нескольких параллельных операций, что особенно полезно для реализации резервных источников данных и ускорения получения данных через несколько источников.
Что будет выведено в консоль и почему?
const promise1 = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Ошибка 1')), 100);
});
const promise2 = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Ошибка 2')), 200);
});
const promise3 = new Promise(resolve => {
setTimeout(() => resolve('Успех 3'), 300);
});
Promise.any([promise1, promise2, promise3])
.then(result => console.log('Результат:', result))
.catch(error => {
if (error instanceof AggregateError) {
console.log('Все провалились:', error.errors.length);
} else {
console.log('Другая ошибка:', error.message);
}
});Ответ: Результат: Успех 3
Объяснение:
Важно понимать, что Promise.any() ждет первый успешный результат, а не просто первый завершенный как в случае с Promise.race().
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪