Promise.race() — это статический метод Promise, который принимает массив промисов и возвращает новый промис, который разрешается или отклоняется с результатом первого завершившегося промиса из массива. Он полезен для реализации таймаутов, обработки гонок между несколькими асинхронными операциями и выбора самого быстрого источника данных.
Основные применения:
“Кто первый ответит — того и тапки!” 🏁
const promise1 = new Promise(resolve =>
setTimeout(() => resolve('Победитель 1'), 500)
);
const promise2 = new Promise(resolve =>
setTimeout(() => resolve('Победитель 2'), 200)
);
const promise3 = new Promise((resolve, reject) =>
setTimeout(() => reject('Ошибка!'), 300)
);
Promise.race([promise1, promise2, promise3])
.then(result => console.log('Победил:', result)) // "Победил: Победитель 2"
.catch(error => console.error('Проиграл:', error));Promise.race() — это мощный инструмент в асинхронном программировании JavaScript, который позволяет выполнять несколько промисов параллельно и работать с результатом первого завершившегося. В отличие от Promise.all(), который ждет завершения всех промисов, Promise.race() реагирует на первый завершенный промис.
Promise.race() принимает итерируемый объект (обычно массив) промисов и возвращает новый промис:
const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 500));
Promise.race([promise1, promise2, promise3])
.then(result => console.log(result)); // 3 (самый быстрый)Одно из самых распространенных применений Promise.race() — добавление таймаута к асинхронным операциям:
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Таймаут')), ms);
});
return Promise.race([promise, timeout]);
}
// Использование
withTimeout(fetch('/api/data'), 5000)
.then(response => console.log('Данные получены'))
.catch(error => {
if (error.message === 'Таймаут') {
console.log('Запрос превысил время ожидания');
} else {
console.error('Ошибка запроса:', error);
}
});Полезно при наличии нескольких источников одних и тех же данных:
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.race(sources);
}
// Использование
fetchFromMultipleSources('/api/user-data')
.then(response => response.json())
.then(data => console.log('Данные получены из самого быстрого источника:', data));Можно использовать для отмены операций при первом сигнале:
function cancellableOperation(operationPromise, cancelSignal) {
return Promise.race([
operationPromise,
cancelSignal.then(() => Promise.reject(new Error('Операция отменена')))
]);
}
// Использование
const longOperation = fetch('/api/long-process');
const cancelButton = document.getElementById('cancel');
const cancelSignal = new Promise((resolve) => {
cancelButton.addEventListener('click', () => resolve());
});
cancellableOperation(longOperation, cancelSignal)
.then(result => console.log('Операция завершена:', result))
.catch(error => {
if (error.message === 'Операция отменена') {
console.log('Операция была отменена пользователем');
}
});function fetchWithTimeout(url, timeoutMs = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Превышено время ожидания')), timeoutMs);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
// Использование
fetchWithTimeout('/api/data', 3000)
.then(response => {
if (!response.ok) throw new Error('Ошибка сети');
return response.json();
})
.then(data => console.log('Данные:', data))
.catch(error => {
if (error.message.includes('Превышено')) {
console.error('Запрос занял слишком много времени');
} else {
console.error('Ошибка:', error.message);
}
});function fetchWithCacheFallback(url) {
const cachePromise = getCachedData(url);
const networkPromise = fetch(url).then(response => response.json());
return Promise.race([cachePromise, networkPromise])
.catch(() => {
// Если первый промис завершился с ошибкой, ждем второй
return Promise.race([networkPromise, cachePromise]);
});
}
// Использование
fetchWithCacheFallback('/api/user-profile')
.then(data => {
console.log('Данные получены (быстрее всего):', data);
return data;
})
.catch(error => console.error('Все источники недоступны:', error));function fetchFromFastestAPI(endpoint) {
const apis = [
fetch(`https://api1.example.com${endpoint}`),
fetch(`https://api2.example.com${endpoint}`),
fetch(`https://api3.example.com${endpoint}`)
];
return Promise.race(apis)
.then(response => {
if (!response.ok) throw new Error('Ошибка API');
return response.json();
});
}
// Использование
fetchFromFastestAPI('/users/123')
.then(userData => {
console.log('Данные пользователя из самого быстрого API:', userData);
})
.catch(error => console.error('Все API недоступны:', error));Если первый завершившийся промис отклоняется, Promise.race() тоже отклоняется:
const successPromise = new Promise(resolve => setTimeout(() => resolve('успех'), 2000));
const errorPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('ошибка')), 1000));
Promise.race([successPromise, errorPromise])
.then(result => console.log('Результат:', result))
.catch(error => console.error('Ошибка:', error.message)); // 'ошибка'Если передать пустой массив, Promise.race() повиснет в состоянии ожидания:
// ❌ Никогда не разрешится
Promise.race([])
.then(result => console.log(result));
// ✅ Проверка на пустой массив
function safeRace(promises) {
if (promises.length === 0) {
return Promise.resolve();
}
return Promise.race(promises);
}Promise.race() преобразует непромисные значения в разрешенные промисы:
Promise.race([42, Promise.resolve('строка'), new Promise(resolve => setTimeout(() => resolve(true), 1000))])
.then(result => console.log(result)); // 42 (число мгновенно преобразуется в разрешенный промис)// ❌ Может пропустить ошибки
Promise.race([fetch('/api/data1'), fetch('/api/data2')])
.then(data => console.log(data));
// ✅ Правильная обработка
Promise.race([fetch('/api/data1'), fetch('/api/data2')])
.then(response => {
if (!response.ok) throw new Error('Ошибка сети');
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Ошибка в гонке:', error));// ❌ Остальные промисы продолжают выполняться
const promises = [slowOperation(), fastOperation()];
Promise.race(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.race(promises)
.then(response => {
controller.abort(); // Отменяем остальные запросы
return response.json();
})
.then(data => console.log(data));Promise.race() — это мощный инструмент для оптимизации асинхронных операций, позволяющий выбирать первый доступный результат из нескольких параллельных операций, что особенно полезно для реализации таймаутов и получения данных из самых быстрых источников.
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪