Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «AbortController у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`AbortController` це вбудований об'єкт, який створює «сигнал скасування» (`AbortSignal`) і дозволяє одним викликом `abort()` перервати асинхронні операції, що цей сигнал слухають.** Контролер тримає стан «скасовано чи ні», а `AbortSignal` це прапорець, який асинхронний код відстежує: має властивість `signal.aborted` і подію `abort`. Сигнал передають у `fetch(url, { signal })`, і після `controller.abort()` запит миттєво обривається з помилкою `AbortError`. ```javascript const controller = new AbortController(); fetch('https://example.com/data', { signal: controller.signal }); controller.abort(); // запит перервано ``` **Ключове:** контролер одноразовий, після `abort()` його сигнал назавжди лишається `aborted === true`, тож для нової операції створюйте новий `AbortController`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`AbortController` це спеціальний об'єкт, який дозволяє створити «сигнал скасування» (`AbortSignal`) і надіслати команду «скасувати» асинхронним операціям, що цей сигнал слухають.** Простіше кажучи: `AbortController` керує станом «скасовано чи ні», а `AbortSignal` це прапорець, який асинхронний код може відстежувати. ## Теорія ### TL;DR - `new AbortController()` створює контролер із двома ключовими членами: `signal` і `abort()`. - `controller.signal` це об'єкт `AbortSignal`, який передають в асинхронну операцію. - `controller.abort()` переводить сигнал у стан «скасовано» і викликає всіх його слухачів. - У `fetch` скасований запит відхиляється помилкою з `err.name === 'AbortError'`. - Сигнал можна підтримати і у власних асинхронних функціях: читати `signal.aborted` та слухати подію `abort`. - Контролер одноразовий: для наступної операції потрібен новий екземпляр. ### Швидкий приклад ```javascript const controller = new AbortController(); // створюємо контролер const signal = controller.signal; // дістаємо з нього сигнал fetch('https://example.com/data', { signal }) .then(response => response.json()) .then(data => console.log(data)) .catch(err => { if (err.name === 'AbortError') { console.log('Запит було скасовано'); } else { console.error('Помилка:', err); } }); // Скасуємо запит через 1 секунду: setTimeout(() => { controller.abort(); // скасування }, 1000); ``` Що відбувається: 1. Створюємо контролер, далі `controller.signal` передаємо у `fetch`. 2. Коли викликаємо `controller.abort()`, `fetch` миттєво переривається і викидає помилку `AbortError`. ### Як це працює під капотом `AbortController` створює об'єкт із двома ключовими властивостями: - `signal` це об'єкт `AbortSignal`, який можна передати в асинхронну операцію; - `abort()` це метод, який активує цей сигнал: змінює його стан і викликає всі зареєстровані обробники. Сам контролер нічого не вміє скасовувати. Він лише перемикає прапорець, а реальну роботу з переривання виконує той API, якому ви віддали сигнал. ### Що вміє AbortSignal `AbortSignal`: - має властивість `.aborted` (булеве значення: `true`, якщо операцію скасовано); - може слухати подію `"abort"`. ```javascript const controller = new AbortController(); const signal = controller.signal; signal.addEventListener('abort', () => { console.log('Операцію скасовано!'); }); controller.abort(); // => "Операцію скасовано!" ``` ### Де можна використовувати `AbortController` підтримується багатьма API: | API | Підтримує скасування? | | --- | --- | | `fetch()` | Так | | `ReadableStream`, `WritableStream` | Так | | `WebSocket` (через власну обгортку) | Частково | | `setTimeout` / `setInterval` (через AbortSignal API у Node 20+ або браузерний polyfill) | Так | | `navigator.sendBeacon()` (у деяких браузерах) | Частково | | Власні async функції | Так, якщо реалізувати підтримку вручну | ### Приклад з Promise і власним скасуванням Сигнал можна використовувати у своїх асинхронних функціях: ```javascript function wait(ms, signal) { return new Promise((resolve, reject) => { const timeout = setTimeout(resolve, ms); signal.addEventListener('abort', () => { clearTimeout(timeout); reject(new Error('Скасовано користувачем')); }); }); } const controller = new AbortController(); wait(5000, controller.signal) .then(() => console.log('Минуло 5 секунд')) .catch(err => console.log(err.message)); setTimeout(() => controller.abort(), 2000); // скасовуємо через 2 секунди ``` Результат: ```text Скасовано користувачем ``` ### Повторне використання і підсумок API Контролер одноразовий: - після `controller.abort()` його `signal` назавжди отримує `.aborted = true`; - щоб скасувати ще раз, потрібно створити **новий** `AbortController`. | Властивість або метод | Що робить | | --- | --- | | `new AbortController()` | створює контролер | | `.signal` | сигнал для передавання в async API | | `.abort()` | надсилає команду «скасувати» | | `signal.aborted` | `true`, якщо скасовано | | `signal.addEventListener('abort', fn)` | слухає подію скасування | ### Типові помилки - **Перевикористання одного контролера для кількох запитів.** Після першого `abort()` сигнал уже «скасований», тож наступний `fetch` із ним впаде одразу. Створюйте новий контролер на кожен запит. - **Ковтання `AbortError` як звичайної помилки.** У `catch` треба окремо перевіряти `err.name === 'AbortError'`, інакше користувач побачить повідомлення про збій там, де запит просто скасували свідомо. - **Очікування, що `abort()` скасує роботу на сервері.** Скасовується лише клієнтська частина: браузер обриває з'єднання, але сервер міг уже виконати операцію. - **Забутий слухач `abort`.** У власних функціях слухача варто прибирати після завершення роботи (наприклад, через `{ once: true }`), інакше сигнал з довгим життям утримуватиме посилання і спричинить витік пам'яті. - **Ігнорування вже скасованого сигналу.** Якщо ви пишете власну async функцію, на початку перевіряйте `signal.aborted`, бо сигнал міг бути скасований ще до її виклику.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.