Як ми знаємо, fetch повертає проміс. А в JavaScript, як правило, немає концепції “переривання” промісу. Отже, як ми можемо перервати поточний fetch? Наприклад, якщо дії користувача на нашому сайті вказують на те, що fetch більше не потрібен.
Для таких цілей є спеціальний вбудований об’єкт: AbortController. Його можна використовувати для переривання не тільки fetch, але й інших асинхронних завдань.
Його використання дуже просте:
Об’єкт AbortController
Створімо контролер:
let controller = new AbortController();
Контролер – це надзвичайно простий об’єкт.
- Він має єдиний метод
abort(), - І єдину властивість
signal, що дозволяє встановлювати на ньому обробники подій.
Коли викликається abort():
controller.signalгенерує подію"abort".- Властивість
controller.signal.abortedстаєtrue.
Як правило, у нас є дві сторони в процесі:
- Та, що виконує операцію, яку можна скасувати, встановлює прослуховувач на
controller.signal. - Та, що скасовує: вона викликає
controller.abort(), коли потрібно.
Ось повний приклад (поки що без fetch):
let controller = new AbortController();
let signal = controller.signal;
// Сторона, що виконує операцію, яку можна скасувати
// отримує об’єкт "signal"
// і налаштовує прослуховувач на тригер, коли викликається controller.abort()
signal.addEventListener('abort', () => alert("переривання!"));
// Інша сторона, що скасовує (в будь-який момент пізніше):
controller.abort(); // переривання!
// Подія запускається, і signal.aborted стає true
alert(signal.aborted); // true
Як ми бачимо, AbortController є лише засобом для передачі подій abort, коли для нього викликається abort().
Ми могли б реалізувати такий самий тип прослуховування подій у нашому коді самостійно, без об’єкта AbortController.
Але цінним є те, що fetch знає, як працювати з об’єктом AbortController. Він інтегрований у нього.
Використання з fetch
Щоб мати можливість скасувати fetch, передайте властивість signal у AbortController як параметр fetch:
let controller = new AbortController();
fetch(url, {
signal: controller.signal
});
Метод fetch знає, як працювати з AbortController. Він прослуховуватиме події abort за signal.
Тепер, щоб перервати, викличте controller.abort():
controller.abort();
Ми закінчили: fetch отримує подію з signal і скасовує запит.
Коли fetch переривається, його проміс завершує виконання з помилкою AbortError, тому ми повинні обробити її, наприклад в try..catch.
Ось повний приклад із fetch, що переривається через 1 секунду:
// перервати через 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 є масштабованим
AbortController є масштабованим. Він дозволяє призупинити кілька fetch одночасно.
Ось приклад коду, який отримує багато URL паралельно та використовує один контролер, щоб скасувати їх усі:
let urls = [...]; // список URL для паралельних fetch
let controller = new AbortController();
// масив промісів fetch
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
}));
// Чекаємо на виконання запитів fetch та наших завдань паралельно
let results = await Promise.all([...fetchJobs, ourJob]);
// якщо controller.abort() викликається з будь-якого місця,
// він перериває всі fetch та ourJob
Підсумки
AbortController– це простий об’єкт, який генерує подіюabortдля своєї властивостіsignalпід час виклику методуabort()(а також встановлює дляsignal.abortedзначенняtrue).fetchінтегрується з ним: ми передаємо властивістьsignalяк параметр, а потімfetchпрослуховує його, тому можна перервати цейfetch.- Ми можемо використовувати
AbortControllerу нашому коді. Взаємодія “викликabort()” → “прослуховування подіїabort” проста та універсальна. Ми можемо використовувати його навіть безfetch.
Коментарі
<code>, для кількох рядків – обгорніть їх тегом<pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)