Skip to main content

Як скасувати виконання проміса?

Чому проміс не можна скасувати напряму

Коли ви створюєте проміс, наприклад:

javascript
const promise = new Promise((resolve) => { setTimeout(() => resolve('done'), 5000); });

Він починає виконуватися одразу (з моменту виклику конструктора), і жодного стандартного механізму, щоб "зупинити" таймер чи fetch, немає. Тобто проміс - це не "потік", а обгортка над результатом, який колись прийде.


Способи реалізації скасування

1. Використати AbortController (вбудований стандарт)

Це найсучасніший і "правильний" спосіб скасування асинхронних операцій, особливо мережевих (fetch).

javascript
const controller = new AbortController(); const { signal } = controller; fetch('/data', { signal }) .then(response => response.json()) .then(data => console.log('Успіх:', data)) .catch(err => { if (err.name === 'AbortError') { console.log('Запит скасовано'); } else { console.error('Помилка:', err); } }); // скасування через 2 секунди setTimeout(() => controller.abort(), 2000);

Що відбувається:

  • AbortController створює об'єкт керування;
  • signal передається у fetch;
  • при controller.abort() запит переривається, і проміс завершується з AbortError.

Працює з:

  • fetch()
  • ReadableStream, Response, Request
  • багатьма бібліотеками, що підтримують сигнали (Axios, Deno, Node 18+ тощо)

2. Створити "скасовуваний" проміс вручну

Можна обгорнути звичайний проміс у структуру з можливістю скасування.

javascript
function makeCancellable(promise) { let isCancelled = false; const wrapped = new Promise((resolve, reject) => { promise .then((val) => (isCancelled ? reject({ cancelled: true }) : resolve(val))) .catch((error) => (isCancelled ? reject({ cancelled: true }) : reject(error))); }); return { promise: wrapped, cancel() { isCancelled = true; }, }; }

Приклад використання:

javascript
const { promise, cancel } = makeCancellable( new Promise((resolve) => setTimeout(() => resolve('Завершено'), 5000)) ); promise .then(console.log) .catch((e) => { if (e.cancelled) console.log('Скасовано користувачем'); }); setTimeout(cancel, 2000);

Через 2 секунди проміс буде "скасований".

Важливо: це логічне скасування, сама операція (наприклад setTimeout чи fetch) все одно триває.


3. Використати прапорець всередині async-функції

Якщо у вас є async-функція, можна перевіряти прапорець "скасування":

javascript
let cancelled = false; async function runTask() { for (let i = 0; i < 5; i++) { if (cancelled) throw new Error('Скасовано'); await new Promise(r => setTimeout(r, 1000)); console.log('Крок', i); } console.log('Готово'); } const task = runTask(); setTimeout(() => (cancelled = true), 2500);

Після 2.5 секунди виконання припиниться.


4. Комбінувати з Promise.race() (скасування через "гонку")

Можна влаштувати "гонку" між операцією і таймером скасування:

javascript
function cancelable(promise, ms) { const cancel = new Promise((_, reject) => setTimeout(() => reject(new Error('Таймаут')), ms) ); return Promise.race([promise, cancel]); } cancelable(fetch('/api/data'), 2000) .then(res => res.json()) .then(console.log) .catch(console.error);

Якщо fetch не встигне за 2 секунди, проміс завершиться з помилкою "Таймаут".


Підсумок

МетодЩо робитьСправжнє скасуванняПідходить для
AbortControllerПерериває операції, що підтримують сигналиТакfetch, API з signal
makeCancellable()Створює обгортку з прапорцемНі (логічне скасування)Користувацькі проміси
Прапорець скасуванняПеревіряє "cancelled" вручнуНіЦикли, async-функції
Promise.race()Завершується першим спрацьованим промісомНі, але зручноТаймаути, конкурентні завдання

Підсумкове правило:

"Скасувати" проміс у JS напряму не можна. Але можна скасувати дію, яку він представляє, за допомогою AbortController, таймаутів або прапорців скасування.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.