Клавиша / esc

Promise.race()

Запускаем несколько промисов и дожидаемся того, который выполнится быстрее.

Время чтения: меньше 5 мин

Кратко

Скопировано

Метод race() — это один из статических методов объекта Promise. Его используют, чтобы запустить несколько промисов и получить результат того, который выполнится быстрее.

Как пишется

Скопировано

Promise.race() принимает итерируемую коллекцию промисов, чаще всего — массив.

Метод возвращает новый промис, который завершится, когда будет получен первый результат или ошибка от переданных промисов. Результаты или ошибки остальных промисов будут проигнорированы.

Как понять

Скопировано

Гонка промисов с успехом

Скопировано

Создадим несколько промисов, завершающихся без ошибок.

        
          
          const slow = new Promise(resolve => setTimeout(() => resolve(1), 6000))const fast = new Promise(resolve => setTimeout(() => resolve(2), 3000))const theFastest = new Promise(resolve => setTimeout(() => resolve(3), 1000))
          const slow = new Promise(resolve => setTimeout(() => resolve(1), 6000))
const fast = new Promise(resolve => setTimeout(() => resolve(2), 3000))
const theFastest = new Promise(resolve => setTimeout(() => resolve(3), 1000))

        
        
          
        
      

Передадим массив из созданных промисов в Promise.race():

        
          
          Promise.race([slow, fast, theFastest])  .then((value) => {    console.log(value)    // 3  })
          Promise.race([slow, fast, theFastest])
  .then((value) => {
    console.log(value)
    // 3
  })

        
        
          
        
      

В консоль запишется результат выполнения theFastest, так как он выполнился быстрее всех.

Гонка промисов с ошибкой

Скопировано

Создадим несколько промисов, где theFastest завершается с ошибкой.

        
          
          const slow = new Promise(  resolve => setTimeout(() => resolve(1), 6000))const fast = new Promise(  resolve => setTimeout(() => resolve(2), 3000))const theFastest = new Promise(  (resolve, reject) => setTimeout(() => reject('Some error'), 1000))
          const slow = new Promise(
  resolve => setTimeout(() => resolve(1), 6000)
)
const fast = new Promise(
  resolve => setTimeout(() => resolve(2), 3000)
)
const theFastest = new Promise(
  (resolve, reject) => setTimeout(() => reject('Some error'), 1000))

        
        
          
        
      

Передадим массив из созданных промисов в Promise.race():

        
          
          Promise.race([slow, fast, theFastest])  .then((value) => {    console.log(value)    // Промис с ошибкой завершился быстрее —    // эта часть проигнорируется  })  .catch((error) => {    console.log(error)    // 'Some error'  })
          Promise.race([slow, fast, theFastest])
  .then((value) => {
    console.log(value)
    // Промис с ошибкой завершился быстрее —
    // эта часть проигнорируется
  })
  .catch((error) => {
    console.log(error)
    // 'Some error'
  })

        
        
          
        
      

В консоль запишется результат выполнения theFastest, так как он завершился быстрее всех.

Пустой список промисов

Скопировано

Если передать в Promise.race() пустой список, то промис навсегда зависнет в состоянии pending:

        
          
          Promise.race([])  .then((value) => {    console.log(value)    // then никогда не сработает  })  .catch((error) => {    console.log(error)    // catch никогда не сработает  })
          Promise.race([])
  .then((value) => {
    console.log(value)
    // then никогда не сработает
  })
  .catch((error) => {
    console.log(error)
    // catch никогда не сработает
  })

        
        
          
        
      

Непромисы в массиве промисов

Скопировано

Создадим массив, где theFastest — завершённый промис, а 3 — элемент, не являющийся промисом.

        
          
          const slow = new Promise(  resolve => setTimeout(() => resolve(1), 6000))const theFastest = Promise.resolve(2)const promises = [slow, theFastest, 3]
          const slow = new Promise(
  resolve => setTimeout(() => resolve(1), 6000)
)
const theFastest = Promise.resolve(2)

const promises = [slow, theFastest, 3]

        
        
          
        
      

Передадим массив из созданных промисов в Promise.race():

        
          
          Promise.race(promises)  .then((value) => {    console.log(value)    // 2  })
          Promise.race(promises)
  .then((value) => {
    console.log(value)
    // 2
  })

        
        
          
        
      

В консоль запишется результат выполнения theFastest, так как в массиве он был первым завершённым промисом. Если поменять порядок элементов, результат изменится:

        
          
          const promises = [slow, 3, theFastest]Promise.race(promises)  .then((value) => {    console.log(value)    // 3  })
          const promises = [slow, 3, theFastest]

Promise.race(promises)
  .then((value) => {
    console.log(value)
    // 3
  })

        
        
          
        
      

Отличие от Promise.any()

Скопировано

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

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

Создадим ещё раз несколько промисов, где theFastest завершается с ошибкой:

        
          
          const slow = new Promise(  resolve => setTimeout(() => resolve(1), 6000))const fast = new Promise(  resolve => setTimeout(() => resolve(2), 3000))const theFastest = new Promise(  (resolve, reject) => setTimeout(() => reject('Some error'), 1000))
          const slow = new Promise(
  resolve => setTimeout(() => resolve(1), 6000)
)
const fast = new Promise(
  resolve => setTimeout(() => resolve(2), 3000)
)
const theFastest = new Promise(
  (resolve, reject) => setTimeout(() => reject('Some error'), 1000)
)

        
        
          
        
      

Передадим массив из созданных промисов в Promise.any():

        
          
          Promise.any([slow, fast, theFastest])  .then((value) => {    console.log(value)    // 2  })  .catch((error) => {    console.log(error)    // Сюда мы не попадём  })
          Promise.any([slow, fast, theFastest])
  .then((value) => {
    console.log(value)
    // 2
  })
  .catch((error) => {
    console.log(error)
    // Сюда мы не попадём
  })

        
        
          
        
      

В консоль запишется результат выполнения fast, так как он выполнился быстрее всех и без ошибки. Этот же пример, но с использованием Promise.race() попадает в catch().

На практике

Скопировано

Сергей Железников советует

Скопировано

🛠 Promise.race() часто используют для реализации таймаута. Например, если нужно ограничить время ожидания ответа от сервера.

Предположим, мы хотим, чтобы запрос выполнялся не дольше 5 секунд. Если сервер не отвечает — считаем это ошибкой. Вот как это можно сделать:

        
          
          function fetchWithTimeout(url, timeout = 5000) {  // Создаём промис, который отклонится через timeout миллисекунд  const timeoutPromise = new Promise((_, reject) => {    setTimeout(() => {      reject(new Error('Превышено время ожидания'))    }, timeout)  });  // Promise.race вернёт новый промис,  // который примет состояние первого завершившегося промиса  return Promise.race([    fetch(url), // Запрос к серверу    timeoutPromise // Таймаут  ]);}
          function fetchWithTimeout(url, timeout = 5000) {

  // Создаём промис, который отклонится через timeout миллисекунд
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error('Превышено время ожидания'))
    }, timeout)
  });

  // Promise.race вернёт новый промис,
  // который примет состояние первого завершившегося промиса
  return Promise.race([
    fetch(url), // Запрос к серверу
    timeoutPromise // Таймаут
  ]);
}

        
        
          
        
      

Мы передаём в Promise.race() массив из двух промисов: запрос к серверу и таймаут.
Promise.race() сразу возвращает новый промис в состоянии pending. Далее он перейдёт в состояние первого завершившегося промиса:

  • выполнен (fulfilled), если первым завершится fetch();
  • отклонён (rejected), если первым сработает таймаут.

Важно: если сработает таймаут, fetch() всё равно продолжит выполняться. Чтобы действительно отменить запрос, используйте AbortController.