@CoolCmd

Отмена выполнения цепочки обещаний (promises). Можно проще?

Есть цепочка обещаний, которая зациклена. Нужно в любой момент извне цепочки прервать её выполнение (вызовом функции "остановить"). Сделал на AbortController:
function создатьОшибкуДляОтменыОбещания()
{
	const e = new Error('The operation was aborted');
	e.name = 'AbortError';
	return e;
}

function проверитьОтменуОбещания(signal)
{
	if (signal.aborted)
	{
		throw создатьОшибкуДляОтменыОбещания();
	}
}

let отменаОбещания = null;

function запустить()
{
	console.assert(отменаОбещания === null);
	отменаОбещания = new AbortController();
	повторить(отменаОбещания.signal);
}

function остановить()
{
	if (отменаОбещания)
	{
		отменаОбещания.abort();
		отменаОбещания = null;
	}
}

function повторить(signal)
{
	fetch('url1', {signal})
	.then(response => response.json())
	.then(
		data =>
		{
			проверитьОтменуОбещания(signal);
			//...
		},
		e =>
		{
			проверитьОтменуОбещания(signal);
			//...
		}
	)
	.then(() => асинхроннаяОперация(signal, 1000))
	.then(() =>
	{
		проверитьОтменуОбещания(signal);
		повторить(signal);
	})
	.catch(e =>
	{
		if (!signal.aborted)
		{
			console.error();
		}
	});
}

function асинхроннаяОперация(signal, ms)
{
	if (signal.aborted)
	{
		return Promise.reject(создатьОшибкуДляОтменыОбещания());
	}
	return new Promise((resolve, reject) =>
	{
		signal.addEventListener('abort', onAbort);
		const idTimeout = setTimeout(() =>
		{
			signal.removeEventListener('abort', onAbort);
			resolve();
		}, ms);
		function onAbort()
		{
			clearTimeout(idTimeout);
			signal.removeEventListener('abort', onAbort);
			reject(создатьОшибкуДляОтменыОбещания());
		}
	});
}

запустить();
остановить();
запустить();

Смущает, что везде нужно добавлять проверитьОтменуОбещания(). Можно как-то проще?

Я видел пару решений, в которых наследовали новый класс от Promise, но там вместо AbortController свой способ уведомления. Кроме того обещания, которые возвращают fetch(), play() и т.д. придется заворачивать в этот самопальный класс, что тоже не радует.
  • Вопрос задан
  • 533 просмотра
Пригласить эксперта
Ответы на вопрос 2
IvanBlacky
@IvanBlacky
back-end разработчик
Можно объявить внешнюю переменную, которая будет флагом, указывающим на то, должна ли цепочка продолжаться (true/false). В цепочке промисов последним звеном вызывать функцию, которая будет вызывать или не будет вызывать цепочку заново, основываясь на значении переменной
Ответ написан
DIITHiTech
@DIITHiTech
Fullstack javascript developer
Использовать внешнюю библиотеку, с которой можно закрывать цепочки обещаний в любом месте.
Простой пример
Усложненный мониторингом прогресса
import CPromise from "c-promise2";

// прерываемый fetch с таймаутом
function fetchWithTimeout(url, options) {
    const {timeout, ...fetchOptions}= options;
    return new CPromise((resolve, reject, {signal}) => {
        fetch(url, {...fetchOptions, signal}).then(resolve, reject)
    }, timeout)
}
        
const chain = fetchWithTimeout("https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=10s", {timeout: 5000})
.then(response => response.json())
.then(response => console.log('Done:', response ));
    
// chain.cancel(); - прервать цепочку промисов и отменить запрос в любое время
// вернет false, если прервать не удалось (в случае, если цепочка была уже выполнена)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы