DevSurge 💦

Как прервать HTTP-запрос в случае долгого отсутствия ответа от сервера

Cover Image for Как прервать HTTP-запрос в случае долгого отсутствия ответа от сервера
Mark Nelyubin
Mark Nelyubin

Когда мы отправляем запрос на сервер, помимо стандартных ошибок сервер может слишком долго обрабатывать запрос. Как прервать запрос по таймауту, если мы используем fetch?

API fetch не имеет встроенной опции тайм-аута. Зато, мы можем использовать интерфейс AbortController совместно с методом встроенного объекта Promise.race(), чтобы реализовать собственный тайм-аут.

Задача

У нас есть несколько GET-запросов через fetch по типу такого:

fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => console.log(json))
      
fetch('https://jsonplaceholder.typicode.com/todos/2')
      .then(response => response.json())
      .then(json => console.log(json))

Мы хотим установить таймер — если сервер не ответил за отведенное время, мы прерываем всю цепочку запросов.

Решение

1. Создаем экземпляр AbortController

const abortController = new AbortController();

Интерфейс AbortController позволяет прерывать один или несколько веб-запросов по мере необходимости. Экземпляр контроллера дает нам доступ к методу abort() и свойству signal

Article Image

2. Создаем сигнал

const abortController = new AbortController();
const signal = abortController.signal;

Свойство signal возвращает экземпляр объекта AbortSignal. Мы используем его, чтобы взаимодействовать с запросом и прерывать его выполнение по желанию.

3. Прикрепляем сигнал к нашим запросам

const abortController = new AbortController();
const signal = abortController.signal;

const fetch1 = fetch('https://jsonplaceholder.typicode.com/todos/1', { signal });
const fetch2 = fetch('https://jsonplaceholder.typicode.com/todos/2', { signal });

Когда мы создаем запрос, передаем signal в качестве опции. Это связывает сигнал и контроллер с запросом.

4. Создаем таймер для прерывания запроса

const abortController = new AbortController();
const signal = abortController.signal;

const fetch1 = fetch('https://jsonplaceholder.typicode.com/todos/1', { signal });
const fetch2 = fetch('https://jsonplaceholder.typicode.com/todos/2', { signal });


const timeout = new Promise((_, reject) => {
  const timeoutId = setTimeout(() => {
    reject(new Error("Время запроса вышло!"));
    abortController.abort(); // Прерви fetch запрос
    clearTimeout(timeoutId); // Очисти таймаут
  }, 5000);
});

Создаем новый промис. Внутри колбека промиса создаем таймаут на 5 секунд. Когда время выйдет — мы вызовем метод abort() нашего контроллера, чтобы прервать запрос и вызовем reject с ошибкой.

5. Группируем наши запросы для прерывания

const abortController = new AbortController();
const signal = abortController.signal;

const fetch1 = fetch('https://jsonplaceholder.typicode.com/todos/1', { signal });
const fetch2 = fetch('https://jsonplaceholder.typicode.com/todos/2', { signal });


const timeout = new Promise((_, reject) => {
  const timeoutId = setTimeout(() => {
    reject(new Error("Время запроса вышло!"));
    abortController.abort(); // Прерви fetch запрос
    clearTimeout(timeoutId); // Очисти таймаут
  }, 5000);
});

// если таймер сработает раньше выполнения любого из запросов — они прервутся
Promise.race([fetch1, fetch2, timeout])
  .then((response) => console.log(response))
  .catch((error) => console.error(error));

Метод Promise.race() принимает список промисов и возвращает один Promise. В данном случае мы передаем 3 промиса — два из них делают запрос на сервер, а третий — содержит таймер для прерывания запроса.

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

Если раньше всех разрешится наш таймаут со статусом rejected (отклонен) — прервутся все остальные промисы в списке (наши GET-запросы), а в консоль попадет наша ошибка. В отличие от Promise.all() нам не придется ждать, пока выполняется или отклонятся все промисы в списке. Даже если отработает один запрос, но второй будет висеть дольше, чем 5 секунд (время нашего таймера), цепочка прервется.

Таким образом, мы можем прервать целую цепочку запросов по истечению таймера.

---

Если вам понравилась статья — не забывайте поделиться ей в своих любимых социальных сетях.


Другие материалы