使用取消方法增强 ES6 Promise

Augment ES6 Promise with cancel method

我正在尝试编写一些代码,在启动一些可能较长的 运行 异步 activity 之后,returns ES6 promise。但是,我希望能够取消 activity,所以我想用 'cancel' 方法来增强我的 Promise。

一个 sscce 说明了我正在尝试做的事情如下:

function TimerPromise(timeInterval) {
    var timer;

    var p = new Promise(
        function(resolve,reject) {
            timer = setTimeout(
                function() {
                    resolve(true);
                },
                timeInterval
            );
        }
    );

    p.cancel = function() {
        clearTimeout(timer);
    };

    console.log("p.cancel is ",p.cancel);

    return p;
}

var t = TimerPromise(2000).then(function(res) { console.log("Result is ",res); });

t.cancel();

示例中TimerPromise只是设置了一个定时器来模拟长运行异步activity.

这是我在 运行 时得到的:

$ node test.js
p.cancel is  function () {
                timer.clearTimeout();
        }
/home/harmic/js/src/test.js:28
t.cancel();
  ^

TypeError: t.cancel is not a function
    at Object.<anonymous> (/home/harmic/js/src/test.js:28:3)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:141:18)
    at node.js:933:3

由于某些原因,我添加到promise中的cancel方法在退出函数后消失了!

有什么原因导致我无法向 ES6 Promise 添加属性或方法吗?或者这是 Promises 的 V8 实现的一些特殊性?

对于奖励积分 - 如果调用取消方法,我希望能够拒绝承诺,如下所示:

p.cancel = function() {
    timer.clearTimeout();
    reject(new Error("Request Cancelled"));
};

但是,我无法在 Promise 执行器外部访问拒绝函数,并且在 Promise 执行器内部我无法访问 Promise 本身(或者我可以吗?)所以我无法在其中扩充 Promise。

这样做有什么合理的模式吗?

注意:我知道 Bluebird 提供可取消的承诺作为扩展。我希望使用 ES6 原生承诺来实现这一点。

我正在使用 node.js 0.12,尽管我希望它也能在当前的浏览器中工作。

问题是对 then 的调用将 return 一个新的 promise 对象,因此您将丢失对使用 cancel 方法的 promise 的引用。

function TimerPromise(timeInterval) {
  var timer;

  var p = new Promise(
    function(resolve, reject) {
      timer = setTimeout(
        function() {
          resolve(true);
        },
        timeInterval
      );
    }
  );

  p.cancel = function() {
    clearTimeout(timer);
  };

  console.log("p.cancel is ", p.cancel);

  return p;
}

var t = TimerPromise(2000);
t.then(function(res) {
  console.log("Result is ", res);
});

t.cancel();

除了 Arun 的回答之外,您还可以很容易地解决 reject 不在范围内的问题,通过引用我在范围内:

var timer;
var reject;

var p = new Promise(
  function(resolve, _reject) {
    reject = _reject;
    timer = setTimeout(
      function() {
         resolve(true);
      },
      timeInterval
    );
  }
);

p.cancel = function() {
  clearTimeout(timer);
  reject(new Error("Request Cancelled")); 
};

我不明白为什么你需要考虑 "cancelling" 承诺。相反,您可以考虑提供一个接口来 拒绝 它。你不需要担心取消计时器,因为如果承诺已经被手动拒绝,那么即使计时器关闭并调用 resolve,它也没有任何效果。

function TimerPromise(timeInterval) {
  var _reject;

  var p = new Promise(function(resolve, reject) {
     // Remember reject callback in outside scope.
      _reject = reject;

      setTimeout(() => resolve(true), timeInterval);
  });

  // Attach reject callback to promise to let it be invocable from outside.
  p.reject= _reject();

  return p;
}

然而,即使在您这样做之后,您也无法取消下游承诺的上游承诺,因为下游承诺不知道它的上游是什么。您必须这样做:

var t1 = TimerPromise(2000);
var t2 = t1.then(function(res) { console.log("Result is ", res); });

t1.reject();

"cancel" 与 promises 相关的术语具有不同的、更复杂的含义,我将不在此赘述。一些图书馆提供该功能;原生 promises 不会,尽管正在讨论将它们添加到 ES 的未来版本中。