如何取消EMCAScript6(香草JavaScript)承诺链

是否有清除JavaScript Promise实例的.then的方法?

我已经在QUnit之上编写了一个JavaScript测试框架。 该框架通过在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);
        });
    });
}

如果测试超时,我的超时Promise将在测试中assert.fail() ,以便测试被标记为失败,这一切都很好,但测试继续运行,因为测试Promise( result )仍在等待解决它。

我需要一个很好的方法来取消我的测试。 我可以通过在框架模块this.cancelTest上创建一个字段来做到这一点,并且每this.cancelTest检查一次(例如,在每个then()迭代的开始处)在测试中是否要取消。 不过,理想情况下,我可以使用$$(at).on("timeout", /* something here */)来清除result变量上剩余的then() ,这样测试的其余部分都不会运行。

有这样的事情存在吗?

快速更新

我尝试使用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()之前拒绝解决。


是否有清除JavaScript Promise实例的.then的方法?

至少不是在ECMAScript 6中。 承诺(和它们的then处理),在默认情况下(不幸)uncancellable。 关于如何以正确的方式进行讨论(例如这里),有一些讨论,但是无论采取哪种方法,都将不会出现在ES6中。

目前的观点是,子类化将允许使用你自己的实现来创建可取消的承诺(不知道它会如何工作)。

在语言委员会找出最佳方式之前(ES7希望?),您仍然可以使用用户区Promise实现,其中许多实现取消功能。

目前的讨论在https://github.com/domenic/cancelable-promise和https://github.com/bergus/promise-cancellation草案中。


虽然在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


我真的很惊讶没有人提到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 });
链接地址: http://www.djcxy.com/p/55453.html

上一篇: How to cancel an EMCAScript6 (vanilla JavaScript) promise chain

下一篇: Why does javascript ES6 Promises continue execution after a resolve?