harmic
harmic

Reputation: 30597

Augment ES6 Promise with cancel method

I'm trying to write some code which returns an ES6 promise after initiating some potentially long running asynchronous activity. However, I would like the possibility of being able to cancel that activity, so I want to augment my Promise with a 'cancel' method.

An sscce which illustrates what I am trying to do is as follows:

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();

In the example TimerPromise just sets a timer to simulate the long running asynchronous activity.

This is what I get when running this:

$ 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

For some reason, the cancel method which I added to the promise has disappeared after leaving the function!

Is there some reason why I cannot add properties or methods to an ES6 Promise? Or is this some peculiarity of the V8 implementation of Promises?

For bonus points - I would like to be able to reject the promise if the cancel method is called, something like this:

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

However, I cannot access the reject function outside the Promise executor, and inside the Promise executor I cannot access the promise itself (or can I?) so I cannot augment the Promise inside there.

Is there any sensible pattern for doing this?

Note: I am aware that Bluebird provides Cancellable promises as an extension. I was hoping to implement this using ES6 native promises.

I am using node.js 0.12, although I'd like this to work in current browsers also.

Upvotes: 0

Views: 571

Answers (3)

user663031
user663031

Reputation:

I don't understand why you need to think in terms of "cancelling" the promise. Instead, you can think in terms of providing an interface to reject it. You don't need to worry about cancelling the timer, because if the promise has already been manually rejected, then even when the timer goes off and resolve is called, it will have no effect.

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;
}

However, even after you do this, you will not be able to cancel an upstream promise from a downstream one, because the downstream one has no idea what is upstream from it. You'll have to do this:

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

t1.reject();

The term "cancel" in relation to promises has a different, more complex meaning, which I will not go into here. Some libraries provide that feature; native promises do not, although there is a discussion underway about adding them to a future version of ES.

Upvotes: 2

Arun P Johny
Arun P Johny

Reputation: 388436

The problem is call to then will return a new promise object, so you are losing the reference to your promise with the cancel method.

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();

Upvotes: 6

Paul
Paul

Reputation: 141927

In addition to Arun's answer you can fix the problem of reject not being in scope pretty easily,by making a reference to it that is I'm scope:

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")); 
};

Upvotes: 2

Related Questions