【问题标题】:Augment ES6 Promise with cancel method使用取消方法增强 ES6 Promise
【发布时间】:2016-06-17 21:09:49
【问题描述】:

我正在尝试编写一些代码,在启动一些可能长时间运行的异步活动后返回一个 ES6 承诺。但是,我希望能够取消该活动,因此我想使用“取消”方法来增加我的 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 只是设置一个计时器来模拟长时间运行的异步活动。

这是我在运行时得到的:

$ 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 原生 Promise 来实现这一点。

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

【问题讨论】:

  • 你可以像往常一样增加promise,你会看到你可以直接调用TimerPromise(2000).cancel(),然后返回一个新的native Promise。
  • 你的 polyfill 不好——你想要第三状态取消语义,因为异常语义不正确。没有一个答案提供了我建议使用的东西 - 相反,您希望继承常规承诺并通过覆盖 then 并添加 finally 来为它们提供该功能。我会在某个时候发布答案。

标签: javascript node.js promise es6-promise cancellation


【解决方案1】:

问题是对then 的调用将返回一个新的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();

【讨论】:

    【解决方案2】:

    除了 Arun 的回答之外,您还可以通过引用 I'm scope 来解决 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")); 
    };
    

    【讨论】:

    • 我明白了您要解决的问题,但您解决它的方式确实看起来很丑陋。将取消实现放在 promise 函数中不是更好吗(它可以随时访问 reject 参数)?
    【解决方案3】:

    我不明白您为什么需要考虑“取消”承诺。相反,您可以考虑提供一个接口来拒绝它。你不必担心取消定时器,因为如果promise已经被手动拒绝了,那么即使定时器关闭并调用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();
    

    与 Promise 相关的术语“取消”具有不同的、更复杂的含义,我在这里不再赘述。一些库提供了该功能;原生 Promise 没有,尽管正在讨论将它们添加到 ES 的未来版本中。

    【讨论】:

    • 在不创建惊人代码方面,这是这里唯一合理的答案。它按照广告宣传并拒绝承诺。 +1
    • 尽管如此,当不再需要计时器时,为提高系统资源的效率执行clearTimeout() 还是有一定价值的。
    • 抱歉反馈迟了。在 SSCCE 中使用计时器是出于演示目的,需要取消的实际异步活动正在使用实际资源并且确实需要取消,而不仅仅是忽略结果。例如,在远程服务器上请求的查询。
    猜你喜欢
    • 1970-01-01
    • 2016-04-15
    • 1970-01-01
    • 2016-07-15
    • 1970-01-01
    • 2018-07-09
    • 2020-10-21
    • 1970-01-01
    • 2017-02-22
    相关资源
    最近更新 更多