Как мы знаем, метод fetch
возвращает промис. А в JavaScript в целом нет понятия «отмены» промиса. Как же прервать запрос fetch
?
Для таких целей существует специальный встроенный объект: AbortController
, который можно использовать для отмены не только fetch
, но и других асинхронных задач.
Использовать его достаточно просто:
-
Шаг 1: создаём контроллер:
let controller = new AbortController();
Контроллер
controller
– чрезвычайно простой объект.- Он имеет единственный метод
abort()
и единственное свойствоsignal
. - При вызове
abort()
:- генерируется событие с именем
abort
на объектеcontroller.signal
- свойство
controller.signal.aborted
становится равнымtrue
.
- генерируется событие с именем
Все, кто хочет узнать о вызове
abort()
, ставят обработчики наcontroller.signal
, чтобы отслеживать его.Вот так (пока без
fetch
):let controller = new AbortController(); let signal = controller.signal; // срабатывает при вызове controller.abort() signal.addEventListener('abort', () => alert("отмена!")); controller.abort(); // отмена! alert(signal.aborted); // true
- Он имеет единственный метод
-
Шаг 2: передайте свойство
signal
опцией в методfetch
:let controller = new AbortController(); fetch(url, { signal: controller.signal });
Метод
fetch
умеет работать сAbortController
, он слушает событиеabort
наsignal
. -
Шаг 3: чтобы прервать выполнение
fetch
, вызовитеcontroller.abort()
:controller.abort();
Вот и всё:
fetch
получает событие изsignal
и прерывает запрос.
Когда fetch
отменяется, его промис завершается с ошибкой AbortError
, поэтому мы должны обработать её, например, в try..catch
:
// прервать через 1 секунду
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal
});
} catch(err) {
if (err.name == 'AbortError') { // обработать ошибку от вызова abort()
alert("Прервано!");
} else {
throw err;
}
}
AbortController
– масштабируемый, он позволяет отменить несколько вызовов fetch
одновременно.
Например, здесь мы запрашиваем много URL параллельно, и контроллер прерывает их все:
let urls = [...]; // список URL для параллельных fetch
let controller = new AbortController();
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
// если откуда-то вызвать controller.abort(),
// то это прервёт все вызовы fetch
Если у нас есть собственные асинхронные задачи, отличные от fetch
, мы можем использовать один AbortController
для их остановки вместе с fetch
.
Нужно лишь слушать его событие abort
:
let urls = [...];
let controller = new AbortController();
let ourJob = new Promise((resolve, reject) => { // наша задача
...
controller.signal.addEventListener('abort', reject);
});
let fetchJobs = urls.map(url => fetch(url, { // запросы fetch
signal: controller.signal
}));
// ожидать выполнения нашей задачи и всех запросов
let results = await Promise.all([...fetchJobs, ourJob]);
// вызов откуда-нибудь ещё:
// controller.abort() прервёт все вызовы fetch и наши задачи
Так что AbortController
существует не только для fetch
, это универсальный объект для отмены асинхронных задач, в fetch
встроена интеграция с ним.
Комментарии вернулись :)
<code>
, для нескольких строк кода — тег<pre>
, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)