Як скасувати виконання проміса?
Чому проміс не можна скасувати напряму
Коли ви створюєте проміс, наприклад:
const promise = new Promise((resolve) => {
setTimeout(() => resolve('done'), 5000);
});Він починає виконуватися одразу (з моменту виклику конструктора),
і жодного стандартного механізму, щоб "зупинити" таймер чи fetch, немає.
Тобто проміс - це не "потік", а обгортка над результатом, який колись прийде.
Способи реалізації скасування
1. Використати AbortController (вбудований стандарт)
Це найсучасніший і "правильний" спосіб скасування асинхронних операцій,
особливо мережевих (fetch).
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. Створити "скасовуваний" проміс вручну
Можна обгорнути звичайний проміс у структуру з можливістю скасування.
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;
},
};
}Приклад використання:
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-функція, можна перевіряти прапорець "скасування":
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() (скасування через "гонку")
Можна влаштувати "гонку" між операцією і таймером скасування:
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, таймаутів або прапорців скасування.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.