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



Когда мы отправляем запрос на сервер, помимо стандартных ошибок сервер может слишком долго обрабатывать запрос. Как прервать запрос по таймауту, если мы используем 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

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 секунд (время нашего таймера), цепочка прервется.
Таким образом, мы можем прервать целую цепочку запросов по истечению таймера.
---
Если вам понравилась статья — не забывайте поделиться ей в своих любимых социальных сетях.