Промисы не имеют встроенного механизма отмены. Из-за этого разработчики придумывают различные хаки, которые порождают некоторые проблемы.
Дополнительный метод .cancel()
Помимо того, что метод .cancel()
делает промис нестандартным, нарушается еще один из главных принципов — только функция, которая создала промис, может изменять его состояние.
Отсутствие очистки после отмены
Некоторые используют Promise.race()
в качестве механизма отмены. Однако, в этом случае у функции, создающей промис, нет возможности узнать, что произошла отмена и нужно освободить некоторые ресурсы.
speculation
Эрик Элиот написал маленький модуль speculation, который решает перечисленные выше проблемы.
Сигнатура функций:
speculation(fn: PromiseFunction, shouldCancel: Promise) => Promise
По аналогии с конструктором Promise
, первым аргументом передается функция «исполнитель», а вторым аргументом передается промис для отмены.
PromiseFunction(resolve: Function, reject: Function, onCancel: Function) => Void
Функция-исполнитель принимает 3 аргумента и реализует логику разрешения, отклонения и отмены. Вот почему у самого промиса не нужен дополнительный метод .cancel()
, и при этом вся логика, связанная с отменой, реализована внутри функции, которая создаёт этот промис.
import speculation from 'speculation';
function doWork(cancel = Promise.reject()) {
speculation(
(resolve, reject, onCancel) => {
// делаем вычисления, которые завершаем вызовом resolve()
onCancel(() => {
// отменяем вычисления и освобождаем ресурсы
reject(new Error('Cancelled'));
});
},
cancel
);
}
Отменённый промис получает статус rejected.
AbortController и AbortSignal
В браузерах начал появляться немного другой механизм для отмены асинхронных операций, возвращающих промисы. Например, fetch()
может принимать объект типа AbortSignal
и останавливать запрос.
const controller = new AbortController();
const signal = controller.signal;
const request = fetch(url, {signal});
// запрос можно отменить, если необходимо
controller.abort();
Значение signal.aborted
меняется только из контроллера. Одновременно с этим испускается событие abort
. В спецификации дан пример, как можно применять
AbortController и AbortSignal для своих API.
Отменённый промис так же получает статус rejected, а значение устанавливается в new DOMException('Aborted', 'AbortError')
.