Проблема при совместном использовании Promise и AbortController

Описание

Сделал такое "дополнение"...

Promise.withSignal = function (callback) {
    const abortController = new AbortController();
    const promise = new Promise((resolve, reject) => callback(abortController.signal, resolve, reject));
    promise.catch(Function).finally(() => abortController.abort());
    return promise;
};

... чтобы облегчить себе жизнь при использовании Promise в слушателях.
Теперь пишу тест для него:

Promise.withSignal = function (callback) {
    const abortController = new AbortController();
    const promise = new Promise((resolve, reject) => callback(abortController.signal, resolve, reject));
    promise.catch(Function).finally(() => abortController.abort());
    return promise;
};

// Promise.withSignal should create a controllable promise
void async function () {
    const body = document.body;
    let counter = 0;
    const promise = Promise.withSignal((signal, resolve) => {
        body.addEventListener(`click`, (event) => resolve(++counter), { signal });
    });
    body.click();
    if (counter !== 1) console.warn(`Expected 1 for first time`);
    await promise;
    body.click();
    if (counter !== 1) console.warn(`Expected 1 for subsequent times`);
}();

Тест показывает, что функция нифига не работает.

Вопрос

Я никак не пойму, в чем проблема.
К тому же где-то полчаса назад она работала, но выдавала другую проблему, из-за чего я ее чуть-чуть подправил.


Дополнительно 1

Вот минимальный пример использования:

Promise.withSignal = function(callback) {
  const abortController = new AbortController();
  const promise = new Promise((resolve, reject) => callback(abortController.signal, resolve, reject));
  promise.catch(Function).finally(() => abortController.abort());
  return promise;
};

void async function () {
  const buttonTrue = document.querySelector(`button#true`);
  const buttonFalse = document.querySelector(`button#false`);

  const choice = await Promise.withSignal((signal, resolve) => {
    buttonTrue.addEventListener(`click`, (event) => resolve(true), { signal });
    buttonFalse.addEventListener(`click`, (event) => resolve(false), { signal });
  });
  console.log(`Your choice is ${choice ? `'true'` : `'false'`}. You can't change it anymore`);
}();
<button id="true">True</button>
<button id="false">False</button>


Ответы (1 шт):

Автор решения: ksa

Я никак не пойму, в чем проблема

Как я и писал выше - твой "аборт" происходит уже после всех событий. Он в таком варианте бесполезен.

Вот посмотри. Я добавил в твой пример вывод в консоль для демонстрации.

Promise.withSignal = function (callback) {
  const abortController = new AbortController();
  const promise = new Promise((resolve, reject) => callback(abortController.signal, resolve, reject));
  promise
    .catch(Function)
    .finally(_ => {
      console.log('finally')
      abortController.abort()
    });
  return promise;
};

// Promise.withSignal should create a controllable promise
void async function () {
  const body = document.body;
  let counter = 0;
  const promise = Promise.withSignal((signal, resolve) => {
    body.addEventListener(`click`, (event) => {
      console.log('click', counter)
      resolve(++counter)
    }, { signal });
  });
  body.click();
  console.log(111, counter)
  if (counter !== 1) console.warn(`Expected 1 for first time`);
  console.log(222, counter)
  await promise;
  console.log(333, counter)
  body.click();
  console.log(444, counter)
  if (counter !== 1) console.warn(`Expected 1 for subsequent times`);
}();

Вот мой вариант... Такое ты хотел получить?

Делать "аборт" нужно в нужное время, извини за тавтологию... :)

Promise.withSignal = function (callback) {
  const abortController = new AbortController();
  const promise = new Promise((resolve, reject) => {
    callback(abortController, resolve, reject)
  });
  promise
    .catch(Function)
    .finally(_ => {
      console.log('finally')
      //abortController.abort()
    });
  return promise;
};

// Promise.withSignal should create a controllable promise
void async function () {
  const body = document.body;
  let counter = 0;
  const promise = Promise.withSignal((abortController, resolve) => {
    body.addEventListener(`click`, (event) => {
      console.log('click', counter)
      resolve(++counter)
      abortController.abort()
    }, { signal: abortController.signal });
  });
  body.click();
  console.log(111, counter)
  if (counter !== 1) console.warn(`Expected 1 for first time`);
  console.log(222, counter)
  await promise;
  console.log(333, counter)
  body.click();
  console.log(444, counter)
  if (counter !== 1) console.warn(`Expected 1 for subsequent times`);
}();

Вот еще вариант придумал... Думаю тебе понравится такой подход. ;)

Promise.withSignal = function (callback) {
    const abortController = new AbortController();
    const promise = new Promise((resolve, reject) => callback(abortController.signal, resolve, reject));
    promise.then(() => abortController.abort(), () => abortController.abort());
    return promise;
};

// Promise.withSignal should create a controllable promise
void async function () {
    const body = document.body;
    let counter = 0;
    const promise = Promise.withSignal((signal, resolve) => {
        body.addEventListener(`click`, (event) => resolve(++counter), { signal });
    });
    body.click();
    if (counter !== 1) console.warn(`Expected 1 for first time`);
    await promise;
    body.click();
    if (counter !== 1) console.warn(`Expected 1 for subsequent times`);
    console.log('Ok', counter)
}();

→ Ссылка