Отменить обычную цепочку обещаний ECMAScript 6

Есть ли способ очистить .then экземпляра JavaScript Promise?

Я написал тестовую среду JavaScript поверх QUnit. Платформа запускает тесты синхронно, выполняя каждый из них в Promise. (Извините за длину этого блока кода. Я прокомментировал его как можно лучше, так что он кажется менее утомительным.)

/* Promise extension -- used for easily making an async step with a
       timeout without the Promise knowing anything about the function 
       it's waiting on */
$$.extend(Promise, {
    asyncTimeout: function (timeToLive, errorMessage) {
        var error = new Error(errorMessage || "Operation timed out.");
        var res, // resolve()
            rej, // reject()
            t,   // timeout instance
            rst, // reset timeout function
            p,   // the promise instance
            at;  // the returned asyncTimeout instance

        function createTimeout(reject, tempTtl) {
            return setTimeout(function () {
                // triggers a timeout event on the asyncTimeout object so that,
                // if we want, we can do stuff outside of a .catch() block
                // (may not be needed?)
                $$(at).trigger("timeout");

                reject(error);
            }, tempTtl || timeToLive);
        }

        p = new Promise(function (resolve, reject) {
            if (timeToLive != -1) {
                t = createTimeout(reject);

                // reset function -- allows a one-time timeout different
                //    from the one original specified
                rst = function (tempTtl) {
                    clearTimeout(t);
                    t = createTimeout(reject, tempTtl);
                }
            } else {
                // timeToLive = -1 -- allow this promise to run indefinitely
                // used while debugging
                t = 0;
                rst = function () { return; };
            }

            res = function () {
                clearTimeout(t);
                resolve();
            };

            rej = reject;
        });

        return at = {
            promise: p,
            resolve: res,
            reject: rej,
            reset: rst,
            timeout: t
        };
    }
});

/* framework module members... */

test: function (name, fn, options) {
    var mod = this; // local reference to framework module since promises
                    // run code under the window object

    var defaultOptions = {
        // default max running time is 5 seconds
        timeout: 5000
    }

    options = $$.extend({}, defaultOptions, options);

    // remove timeout when debugging is enabled
    options.timeout = mod.debugging ? -1 : options.timeout;

    // call to QUnit.test()
    test(name, function (assert) {
        // tell QUnit this is an async test so it doesn't run other tests
        // until done() is called
        var done = assert.async();
        return new Promise(function (resolve, reject) {
            console.log("Beginning: " + name);

            var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
            $$(at).one("timeout", function () {
                // assert.fail() is just an extension I made that literally calls
                // assert.ok(false, msg);
                assert.fail("Test timed out");
            });

            // run test function
            var result = fn.call(mod, assert, at.reset);

            // if the test returns a Promise, resolve it before resolving the test promise
            if (result && result.constructor === Promise) {
                // catch unhandled errors thrown by the test so future tests will run
                result.catch(function (error) {
                    var msg = "Unhandled error occurred."
                    if (error) {
                        msg = error.message + "\n" + error.stack;
                    }

                    assert.fail(msg);
                }).then(function () {
                    // resolve the timeout Promise
                    at.resolve();
                    resolve();
                });
            } else {
                // if test does not return a Promise, simply clear the timeout
                // and resolve our test Promise
                at.resolve();
                resolve();
            }
        }).then(function () {
            // tell QUnit that the test is over so that it can clean up and start the next test
            done();
            console.log("Ending: " + name);
        });
    });
}

Если время ожидания теста истекло, мое обещание тайм-аута будет assert.fail() на тесте, так что тест будет помечен как неудачный, что все хорошо, но тест продолжает выполняться, потому что тестовое обещание (result) все еще ожидает его разрешения.

Мне нужен хороший способ отменить тест. Я могу сделать это, создав поле в модуле фреймворка this.cancelTest или что-то в этом роде и время от времени проверяя (например, в начале каждой then() итерации) в тесте, нужно ли отменять. Однако в идеале я мог бы использовать $$(at).on("timeout", /* something here */), чтобы очистить оставшиеся then() в моей переменной result, чтобы ни один из остальных тестов не запускался.

Что-то подобное существует?

Быстрое обновление

Я пробовал использовать Promise.race([result, at.promise]). Это не сработало.

Обновление 2 + путаница

Чтобы разблокировать себя, я добавил несколько строк с mod.cancelTest / polling в идею теста. (Я также удалил триггер события.)

return new Promise(function (resolve, reject) {
    console.log("Beginning: " + name);

    var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
    at.promise.catch(function () {
        // end the test if it times out
        mod.cancelTest = true;
        assert.fail("Test timed out");
        resolve();
    });

    // ...
    
}).then(function () {
    // tell QUnit that the test is over so that it can clean up and start the next test
    done();
    console.log("Ending: " + name);
});

Я установил точку останова в операторе catch, и он был достигнут. Что меня сейчас сбивает с толку, так это то, что оператор then() не вызывается. Идеи?

Обновление 3

Прикинул последнее. fn.call() выдавал ошибку, которую я не уловил, поэтому тестовое обещание было отклонено до того, как at.promise.catch() смог ее разрешить.


person dx_over_dt    schedule 06.04.2015    source источник
comment
Можно выполнить отмену с помощью обещаний ES6, но это не свойство обещания (скорее, это свойство функции, которая его возвращает). Я могу привести небольшой пример, если вам интересно.   -  person Benjamin Gruenbaum    schedule 07.04.2015
comment
@BenjaminGruenbaum Я знаю, что прошел почти год, но мне все еще интересно, есть ли у вас время, чтобы написать пример. :)   -  person dx_over_dt    schedule 31.05.2016
comment
Это было год назад, но это было официально обсуждено за два дня до вчерашнего дня с переходом токенов отмены и отменяемых обещаний на этап 1.   -  person Benjamin Gruenbaum    schedule 31.05.2016
comment
Ответ ES6 на отмену обещания - наблюдаемый. Подробнее об этом можно прочитать здесь: github.com/Reactive-Extensions/RxJS   -  person Frank Goortani    schedule 26.01.2017
comment
Связывание моего ответа на использование библиотеки Prex для отмены обещания.   -  person noseratio    schedule 01.11.2018


Ответы (17)


Есть ли способ очистить .then экземпляра JavaScript Promise?

Нет. По крайней мере, не в ECMAScript 6. Обещания (и их then обработчики) по умолчанию не могут быть отменены (к сожалению). На es-Discussion есть небольшое обсуждение (например, здесь) о том, как это сделать. правильным образом, но какой бы подход ни выиграл, он не попадет в ES6.

Текущая точка зрения заключается в том, что создание подклассов позволит создавать отменяемые обещания с использованием вашей собственной реализации (не уверен, насколько хорошо это будет работать).

Пока языковой комитет не определит лучший способ (надеюсь, ES7?), вы все еще можете использовать пользовательские реализации Promise, многие из которых имеют отмену.

Текущее обсуждение находится в https://github.com/domenic/cancelable-promise и https://github.com/bergus/promise-cancellation черновики.

person Bergi    schedule 06.04.2015
comment
Немного обсуждения - я могу сослаться на около 30 тем на esdiscuss или GitHub :) (не говоря уже о вашей собственной помощи с отменой в bluebird 3.0) - person Benjamin Gruenbaum; 07.04.2015
comment
@BenjaminGruenbaum: У вас есть эти ссылки, которыми можно где-нибудь поделиться? Я давно хотел обобщить мнения и попытки и опубликовать предложение на esdiscuss, поэтому буду рад, если смогу проверить, что я ничего не забыл. - person Bergi; 07.04.2015
comment
У меня они есть под рукой на работе - так что через 3-4 дня они будут. Вы можете проверить спецификацию отмены обещаний в разделе promises-aplus для хорошего начала. - person Benjamin Gruenbaum; 07.04.2015
comment
@BenjaminGruenbaum: Спасибо, я легко могу этого дождаться. Кстати, я начал с promises-aplus :-) - person Bergi; 07.04.2015
comment
Интересно, что люди из Haskell пишут по этому поводу: Немного. Особого интереса вроде нет. Может быть, этого вообще можно избежать с помощью правильной конструкции Promise цепочки. Когда я сочиняю обычные функции, я никогда не чувствовал необходимости преждевременно выходить из композиции. Поскольку Promise цепочки - это просто композиции неблокирующих функций ... - person ; 09.07.2016
comment
@ LUH3417: обычные функции в этом плане просто скучны. Вы запускаете программу и ждете, пока она не завершится - или вы kill ее и игнорируете, в каком, возможно, странном состоянии, побочные эффекты оставили вашу среду (так что вы обычно просто выбрасываете и это, например, любые незавершенные результаты). Однако неблокирующие или асинхронные функции созданы для работы в интерактивных приложениях, где вы хотите иметь такой более тонкий контроль над выполнением текущих операций. - person Bergi; 10.07.2016
comment
@ft или один вариант использования, который я нашел, - это если вы хотите отслеживать последнее обещание, добавленное в цепочку. Другими словами, доступный эквивалент ветки GIT HEAD. Единственный способ, которым я могу это сделать, без запуска отдельного счетчика, - это поддерживать пригодный для использования указатель и отключать его от текущей головы и подключать к новой голове при добавлении ссылки в цепочку. Насколько я могу понять, это невозможно без возможности отказаться от подписки. Если вы можете отказаться от подписки, то очень легко создать очередь FIFO на основе обещаний с событием стока. - person Cool Blue; 24.10.2016
comment
Доменик удалил предложение TC39 ... ... cc @BenjaminGruenbaum - person Sergio; 20.12.2016

Хотя в ES6 нет стандартного способа сделать это, для этого есть библиотека под названием Bluebird.

Существует также рекомендуемый способ, описанный в документации по реакции. Это похоже на то, что у вас было во 2-м и 3-м обновлениях.

const makeCancelable = (promise) => {
  let hasCanceled_ = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then((val) =>
      hasCanceled_ ? reject({isCanceled: true}) : resolve(val)
    );
    promise.catch((error) =>
      hasCanceled_ ? reject({isCanceled: true}) : reject(error)
    );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled_ = true;
    },
  };
};

const cancelablePromise = makeCancelable(
  new Promise(r => component.setState({...}}))
);

cancelablePromise
  .promise
  .then(() => console.log('resolved'))
  .catch((reason) => console.log('isCanceled', reason.isCanceled));

cancelablePromise.cancel(); // Cancel the promise

Взято из: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html

person Michael Yagudaev    schedule 27.05.2016
comment
это определение отменено просто отвергает обещание. это зависит от определения отмененного. - person Alexander Mills; 01.02.2019
comment
А что произойдет, если вы захотите отменить набор обещаний? - person Matthieu Brucher; 01.05.2019
comment
Проблема с этим подходом в том, что если у вас есть обещание, которое никогда не будет разрешено или отклонено, оно никогда не будет отменено. - person DaNeSh; 29.08.2019
comment
Отчасти это правильно, но если у вас длинная цепочка обещаний, этот подход не сработает. - person Veikko Karsikko; 28.11.2019

Я действительно удивлен, что никто не упоминает Promise.race в качестве кандидата на это:

const actualPromise = new Promise((resolve, reject) => { setTimeout(resolve, 10000) });
let cancel;
const cancelPromise = new Promise((resolve, reject) => {
    cancel = reject.bind(null, { canceled: true })
})

const cancelablePromise = Object.assign(Promise.race([actualPromise, cancelPromise]), { cancel });
person Pho3nixHun    schedule 16.04.2018
comment
Я не верю, что это работает. Если вы измените обещание на журнал, запуск cancel() все равно приведет к вызову журнала. `` `const actualPromise = новое обещание ((разрешить, отклонить) =› {setTimeout (() = ›{console.log ('фактический вызов'); resolve ()}, 10000)}); `` '' - person shmck; 11.06.2018
comment
Вопрос заключался в том, как отменить обещание (= ›остановить выполнение thens для выполнения), а не как отменить setTimeout (=› _ 3_) или синхронный код, где, если вы не поставите if после каждой строки (if (canceled) return), этого нельзя достичь. (Не делай этого) - person Pho3nixHun; 24.06.2018

const makeCancelable = promise => {
    let rejectFn;

    const wrappedPromise = new Promise((resolve, reject) => {
        rejectFn = reject;

        Promise.resolve(promise)
            .then(resolve)
            .catch(reject);
    });

    wrappedPromise.cancel = () => {
        rejectFn({ canceled: true });
    };

    return wrappedPromise;
};

Использование:

const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
person Slava M    schedule 06.06.2019

Обещание можно отменить с помощью AbortController.

Есть ли способ очистки: да, вы можете отклонить обещание с помощью объекта AbortController, и тогда promise обойдет все блоки then и перейдет непосредственно к блоку catch.

Пример:

import "abortcontroller-polyfill";

let controller = new window.AbortController();
let signal = controller.signal;
let elem = document.querySelector("#status")

let example = (signal) => {
    return new Promise((resolve, reject) => {
        let timeout = setTimeout(() => {
            elem.textContent = "Promise resolved";
            resolve("resolved")
        }, 2000);

        signal.addEventListener('abort', () => {
            elem.textContent = "Promise rejected";
            clearInterval(timeout);
            reject("Promise aborted")
        });
    });
}

function cancelPromise() {
    controller.abort()
    console.log(controller);
}

example(signal)
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.log("Catch: ", error)
    });

document.getElementById('abort-btn').addEventListener('click', cancelPromise);

HTML


    <button type="button" id="abort-btn" onclick="abort()">Abort</button>
    <div id="status"> </div>

Примечание: необходимо добавить полифилл, поддерживается не всеми браузерами.

Живой пример

 Редактировать Elegant-lake-5jnh3

person Sohail Ashraf    schedule 31.01.2020

На самом деле невозможно остановить выполнение обещания, но вы можете перехватить отклонение и вызвать его из самого обещания.

class CancelablePromise {
  constructor(executor) {
    let _reject = null;
    const cancelablePromise = new Promise((resolve, reject) => {
      _reject = reject;
      return executor(resolve, reject);
    });
    cancelablePromise.cancel = _reject;

    return cancelablePromise;
  }
}

Использование:

const p = new CancelablePromise((resolve, reject) => {
  setTimeout(() => {
    console.log('resolved!');
    resolve();
  }, 2000);
})

p.catch(console.log);

setTimeout(() => {
  p.cancel(new Error('Messed up!'));
}, 1000);
person nikksan    schedule 12.08.2019
comment
@dx_over_dt Ваше изменение было бы отличным комментарием, но не редактированием. Пожалуйста, оставьте такие существенные правки на усмотрение OP (если, конечно, сообщение не помечено как Community Wiki). - person TylerH; 12.08.2019
comment
@TylerH так есть смысл редактировать, чтобы исправить опечатки и тому подобное? Или обновлять информацию по мере ее устаревания? Я новичок в возможности редактировать права на сообщения других людей. - person dx_over_dt; 12.08.2019
comment
@dx_over_dt Да, редактирование предназначено для улучшения сообщений путем исправления опечаток, грамматических ошибок и добавления подсветки синтаксиса (например, если кто-то просто публикует кучу кода, но не делает отступ и не помечает его с помощью `` ''). Добавление существенного содержания, такого как дополнительные объяснения или рассуждения / оправдания, обычно входит в компетенцию человека, опубликовавшего ответ. Вы можете предложить это в комментариях, и OP будет уведомлен о комментарии и сможет ответить на него, или они могут просто включить ваше предложение в сообщение. - person TylerH; 12.08.2019
comment
@dx_over_dt Исключения составляют сообщения, помеченные как Community Wiki, указывающие на то, что они предназначены для совместной работы (например, в Википедии), или если есть серьезные проблемы с сообщением, такие как грубые / оскорбительные выражения, опасный / вредный контент (например, предложения или код, который может передать вам вирус или привести к аресту и т. д.), или личную информацию, такую ​​как медицинские записи, номера телефонов, кредитные карты и т. д .; не стесняйтесь удалять их самостоятельно. - person TylerH; 12.08.2019
comment
Стоит отметить, что выполнение не может быть остановлено в рамках обещания, потому что JavaScript является однопоточным. Пока выполняется функция обещания, больше ничего не выполняется, поэтому нет ничего, что могло бы вызвать остановку выполнения. - person dx_over_dt; 12.08.2019
comment
Сработает ли это, если executor вернет обещание с catch в нем? Предположим, что в моем сценарии модульного тестирования я ожидал ошибки и хотел проверить, восстановится ли функция. Будет ли вызов cancel возобновить выполнение из этого внутреннего catch или отменить тест, как задумано? - person dx_over_dt; 16.12.2019

Есть несколько библиотек npm для отменяемых обещаний.

  1. p-cancelable https://github.com/sindresorhus/p-cancelable < / а>

  2. cancelable-обещание https://github.com/alkemics/CancelablePromise

person WebBrother    schedule 29.11.2018

Вот наша реализация https://github.com/permettez-moi-de-construire/cancellable-promise

Используется как

const {
  cancellablePromise,
  CancelToken,
  CancelError
} = require('@permettezmoideconstruire/cancellable-promise')

const cancelToken = new CancelToken()

const initialPromise = SOMETHING_ASYNC()
const wrappedPromise = cancellablePromise(initialPromise, cancelToken)


// Somewhere, cancel the promise...
cancelToken.cancel()


//Then catch it
wrappedPromise
.then((res) => {
  //Actual, usual fulfill
})
.catch((err) => {
  if(err instanceOf CancelError) {
    //Handle cancel error
  }

  //Handle actual, usual error
})

который :

  • Не трогает Promise API
  • Сделаем дальнейшую отмену внутри catch звонка
  • Положитесь на то, что отмена будет отклонена, а не решена, в отличие от любого другого предложения или реализации.

Тяги и комментарии приветствуются

person Cyril CHAPON    schedule 26.03.2018

простая версия:

просто выдайте функцию отклонения.

function Sleep(ms,cancel_holder) {

 return new Promise(function(resolve,reject){
  var done=false; 
  var t=setTimeout(function(){if(done)return;done=true;resolve();}, ms);
  cancel_holder.cancel=function(){if(done)return;done=true;if(t)clearTimeout(t);reject();} 
 })
}

упаковочный раствор (заводской)

решение, которое я нашел, - передать объект cancel_holder. у него будет функция отмены. если у него есть функция отмены, то его можно отменить.

Эта функция отмены отклоняет обещание с ошибкой («отменено»).

Перед разрешением, отклонением или on_cancel предотвращает беспричинный вызов функции отмены.

Я нашел удобным передать действие отмены путем инъекции

function cancelablePromise(cancel_holder,promise_fn,optional_external_cancel) {
  if(!cancel_holder)cancel_holder={};
  return new Promise( function(resolve,reject) {
    var canceled=false;
    var resolve2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; resolve.apply(this,arguments);}
    var reject2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; reject.apply(this,arguments);}
    var on_cancel={}
    cancel_holder.cancel=function(){
      if(canceled) return; canceled=true;

      delete cancel_holder.cancel;
      cancel_holder.canceled=true;

      if(on_cancel.cancel)on_cancel.cancel();
      if(optional_external_cancel)optional_external_cancel();

      reject(new Error('canceled'));
    };

    return promise_fn.call(this,resolve2,reject2,on_cancel);        
  });
}

function Sleep(ms,cancel_holder) {

 return cancelablePromise(cancel_holder,function(resolve,reject,oncacnel){

  var t=setTimeout(resolve, ms);
  oncacnel.cancel=function(){if(t)clearTimeout(t);}     

 })
}


let cancel_holder={};

// meanwhile in another place it can be canceled
setTimeout(function(){  if(cancel_holder.cancel)cancel_holder.cancel(); },500) 

Sleep(1000,cancel_holder).then(function() {
 console.log('sleept well');
}, function(e) {
 if(e.message!=='canceled') throw e;
 console.log('sleep interrupted')
})
person Shimon Doodkin    schedule 19.09.2017

Попробуйте прерывание обещания: https://www.npmjs.com/package/promise-abortable

$ npm install promise-abortable
import AbortablePromise from "promise-abortable";

const timeout = new AbortablePromise((resolve, reject, signal) => {
  setTimeout(reject, timeToLive, error);
  signal.onabort = resolve;
});

Promise.resolve(fn()).then(() => {
  timeout.abort();
});
person Devi    schedule 28.06.2019

Если ваш код помещен в класс, вы можете использовать для этого декоратор. Такой декоратор у вас есть в utils-decorators (npm install --save utils-decorators). Он отменит предыдущий вызов декорированного метода, если до разрешения предыдущего вызова был сделан еще один вызов для этого конкретного метода.

import {cancelPrevious} from 'utils-decorators';

class SomeService {

   @cancelPrevious()
   doSomeAsync(): Promise<any> {
    ....
   }
}

или вы можете использовать функцию-оболочку:

import {cancelPreviousify} from 'utils-decorators';

const cancelable = cancelPreviousify(originalMethod)

https://github.com/vlio20/utils-decorators#cancelprevious-method

person vlio20    schedule 16.04.2020

Если вы хотите остановить выполнение всех событий и ловушек, вы можете сделать это, введя обещание, которое никогда не будет выполнено. Вероятно, это повторяет утечку памяти, но это решит проблему и не должно вызывать слишком много потраченной впустую памяти в большинстве приложений.

new Promise((resolve, reject) => {
    console.log('first chain link executed')
    resolve('daniel');
}).then(name => {
    console.log('second chain link executed')
    if (name === 'daniel') {
        // I don't want to continue the chain, return a new promise
        // that never calls its resolve function
        return new Promise((resolve, reject) => {
            console.log('unresolved promise executed')
        });
    }
}).then(() => console.log('last chain link executed'))

// VM492:2 first chain link executed
// VM492:5 second chain link executed
// VM492:8 unresolved promise executed
person DanLatimer    schedule 13.04.2017

Установите свойство «отменено» в обещании, чтобы сигнализировать then() и catch() о досрочном выходе. Это очень эффективно, особенно для Web Workers, у которых существующие микрозадачи поставлены в очередь в Promises от onmessage обработчиков.

// Queue task to resolve Promise after the end of this script
const promise = new Promise(resolve => setTimeout(resolve))

promise.then(_ => {
  if (promise.canceled) {
    log('Promise cancelled.  Exiting early...');
    return;
  }

  log('No cancelation signaled.  Continue...');
})

promise.canceled = true;

function log(msg) {
  document.body.innerHTML = msg;
}

person AnthumChris    schedule 30.03.2018

Ответ @Michael Yagudaev у меня работает.

Но исходный ответ не связывал обернутое обещание с .catch () для обработки обработки отклонения, вот мое улучшение поверх ответа @Michael Yagudaev:

const makeCancelablePromise = promise => {
  let hasCanceled = false;
  const wrappedPromise = new Promise((resolve, reject) => {
    promise
      .then(val => (hasCanceled ? reject({ isCanceled: true }) : resolve(val)))
      .catch(
        error => (hasCanceled ? reject({ isCanceled: true }) : reject(error))
      );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled = true;
    }
  };
};

// Example Usage:
const cancelablePromise = makeCancelable(
  new Promise((rs, rj) => {
    /*do something*/
  })
);
cancelablePromise.promise.then(() => console.log('resolved')).catch(err => {
  if (err.isCanceled) {
    console.log('Wrapped promise canceled');
    return;
  }
  console.log('Promise was not canceled but rejected due to errors: ', err);
});
cancelablePromise.cancel();
person Community    schedule 03.08.2018

Если p - это переменная, содержащая обещание, тогда p.then(empty); следует отклонить обещание, когда оно в конечном итоге завершится или если оно уже завершено (да, я знаю, что это не исходный вопрос, но это мой вопрос). "пусто" равно function empty() {}. Я просто новичок и, вероятно, ошибаюсь, но эти другие ответы кажутся слишком сложными. Обещания должны быть простыми.

person David Spector    schedule 20.12.2018

Я все еще работаю над этой идеей, но вот как я реализовал отменяемое обещание на примере setTimeout.

Идея состоит в том, что обещание разрешается или отклоняется всякий раз, когда вы решили, что это так, поэтому нужно решить, когда вы хотите отменить, удовлетворить критерию, а затем самостоятельно вызвать функцию reject().

  • Во-первых, я думаю, что есть две причины для досрочного завершения обещания: чтобы завершить его (что я назвал решить) и отменить (что я назвал отклонить ). Конечно, это только мое ощущение. Конечно, есть метод Promise.resolve(), но он находится в самом конструкторе и возвращает фиктивное разрешенное обещание. Этот метод экземпляра resolve() фактически разрешает созданный экземпляр объекта обещания.

  • Во-вторых, вы можете с радостью добавить к вновь созданному объекту обещания все, что захотите, прежде чем вернуть его, поэтому я только что добавил методы resolve() и reject(), чтобы сделать его самодостаточным.

  • В-третьих, уловка состоит в том, чтобы иметь возможность позже получить доступ к функциям исполнителя resolve и reject, поэтому я просто сохранил их в простом объекте внутри замыкания.

Я думаю, что решение простое, и я не вижу в нем серьезных проблем.

function wait(delay) {
  var promise;
  var timeOut;
  var executor={};
  promise=new Promise(function(resolve,reject) {
    console.log(`Started`);
    executor={resolve,reject};  //  Store the resolve and reject methods
    timeOut=setTimeout(function(){
      console.log(`Timed Out`);
      resolve();
    },delay);
  });
  //  Implement your own resolve methods,
  //  then access the stored methods
      promise.reject=function() {
        console.log(`Cancelled`);
        clearTimeout(timeOut);
        executor.reject();
      };
      promise.resolve=function() {
        console.log(`Finished`);
        clearTimeout(timeOut);
        executor.resolve();
      };
  return promise;
}

var promise;
document.querySelector('button#start').onclick=()=>{
  promise=wait(5000);
  promise
  .then(()=>console.log('I have finished'))
  .catch(()=>console.log('or not'));
};
document.querySelector('button#cancel').onclick=()=>{ promise.reject(); }
document.querySelector('button#finish').onclick=()=>{ promise.resolve(); }
<button id="start">Start</button>
<button id="cancel">Cancel</button>
<button id="finish">Finish</button>

person Manngo    schedule 08.04.2020

Используя пакет CPromise, мы можем использовать следующий подход (Живая демонстрация)

import CPromise from "c-promise2";

const chain = new CPromise((resolve, reject, { onCancel }) => {
  const timer = setTimeout(resolve, 1000, 123);
  onCancel(() => clearTimeout(timer));
})
  .then((value) => value + 1)
  .then(
    (value) => console.log(`Done: ${value}`),
    (err, scope) => {
      console.warn(err); // CanceledError: canceled
      console.log(`isCanceled: ${scope.isCanceled}`); // true
    }
  );

setTimeout(() => {
  chain.cancel();
}, 100);

То же самое с AbortController (Живая демонстрация)

import CPromise from "c-promise2";

const controller= new CPromise.AbortController();

new CPromise((resolve, reject, { onCancel }) => {
  const timer = setTimeout(resolve, 1000, 123);
  onCancel(() => clearTimeout(timer));
})
  .then((value) => value + 1)
  .then(
    (value) => console.log(`Done: ${value}`),
    (err, scope) => {
      console.warn(err);
      console.log(`isCanceled: ${scope.isCanceled}`);
    }
  ).listen(controller.signal);

setTimeout(() => {
  controller.abort();
}, 100);
person Dmitriy Mozgovoy    schedule 25.11.2020