Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cancellation support #123

Merged

Conversation

not-an-aardvark
Copy link
Contributor

Fixes #113

This change adds bluebird's .cancel function to all request objects from request-promise. When the .cancel function is called, the Promise is cancelled, and the corresponding request is aborted.

@coveralls
Copy link

coveralls commented Jun 9, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 418093f on not-an-aardvark:113-cancellation-support into 061a6ae on request:master.

@analog-nico
Copy link
Member

Awesome, thank you @not-an-aardvark !

I am in the middle of doing a major refactoring for request-promise and this makes my job easier. However, please be a little patient until I release the next version.

@horejsek
Copy link

Can't wait for new version with this feature. Do you know, @analog-nico, when you will approximately finish refactor? :-)

@analog-nico
Copy link
Member

Awesome timing @horejsek ! 😄 I just release the new request-promise version yesterday which completes the refactoring. Now it is time for new features and this one is on the top of my list.

@horejsek
Copy link

Nice! 👍

@nhducit
Copy link

nhducit commented Jul 30, 2016

Can i use cancel promise in the latest version?

@not-an-aardvark
Copy link
Contributor Author

not-an-aardvark commented Jul 30, 2016

An update on this PR:

I've been trying to figure out a way to reimplement cancellation semantics in request-promise 4, but at the moment I'm unsure of whether this is possible without adding a hook to the @request/promise-core module.

The issue is that while bluebird allows a cancellation listener to be added from the promise constructor (e.g. new Bluebird(resolve, reject, onCancel)), it does not support adding cancellation listeners to existing Promises. Since the Promise is initially created in@request/promise-core, the cancellation hook would need to be added into that module.

As an alternative, I considered something like this in request-promise:

request.Request.prototype.cancel = function() {
    this._rp_promise.cancel();
    this.abort();
};

While this aborts the request if cancel is called directly on the request object, it does not work if Promises are chained from it:

var promise = request.get('http://example.com').then(function() {
  // handle response
});

promise.cancel(); // does not abort the request

So I think it is necessary to add a hook to @request/promise-core. I'm not sure of the best way to do that, since @request/promise-core is supposed to be agnostic to the Promise library that is used. Maybe it would be best to add an optional override for the Promise constructor function, e.g.

var configure = require('@request/promise-core/configure/request2');
configure({
  request: request,
  PromiseImpl: Bluebird,
  expose: [
    'then',
    'catch',
    'finally',
    'cancel',
    'promise'
  ],
  constructorOverride: function (resolve, reject, onCancel) {
    this._rp_resolve = resolve;
    this._rp_reject = reject;
    onCancel(function() {
      this.abort();
    });
  }
});

...but this solution seems a bit inelegant, since request-promise should not need to set the private properties _rp_resolve and _rp_reject that are used in @request/promise-core.

@analog-nico, do you have any suggestions for how to handle this issue?

@analog-nico
Copy link
Member

Hi @not-an-aardvark , thanks for putting your thoughts into this!!!

Your constructorOverride is a neat idea. I agree that setting _rp_resolve and _rp_reject is inelegant so I would extend your idea and call it constructorMixin.

@request/promise-core would be extended like this:

        self._rp_promise = new PromiseImpl(function (resolve, reject) {
            self._rp_resolve = resolve;
            self._rp_reject = reject;
+           if (options.constructorMixin) {
+               options.constructorMixin.apply(self, arguments);
+           }
        });

And then request-promise could look like this:

var configure = require('@request/promise-core/configure/request2');
configure({
  request: request,
  PromiseImpl: Bluebird,
  expose: [
    'then',
    'catch',
    'finally',
    'cancel',
    'promise'
  ],
  constructorMixin: function (resolve, reject, onCancel) {
    var self = this;
    onCancel(function() {
      self.abort();
    });
  }
});

Additionally, I see the following todos:

  • Bluebird.config({cancellation: true}) is a global setting. If the request-promise user uses Bluebird in the main project as well and npm doesn't install different versions of Bluebird – this is likely – request-promise would activate cancellation for the whole project. I wonder if there is a way around that.
  • .abort() should be overwritten to also cancel the promise.

@analog-nico
Copy link
Member

@nhducit We are working on it. I'll let everyone know in this PR once a new version is published that supports cancellation.

@not-an-aardvark
Copy link
Contributor Author

-snip-

Thanks, constructorMixin sounds like a good solution to me.

Bluebird.config({cancellation: true}) is a global setting. If the request-promise user uses Bluebird in the main project as well and npm doesn't install different versions of Bluebird – this is likely – request-promise would activate cancellation for the whole project. I wonder if there is a way around that.

Bluebird 3.4.1 has a getNewLibraryCopy function, which creates a pristine version of Bluebird to avoid modifying global state like you described.

@analog-nico
Copy link
Member

Alright. I am on it.

@not-an-aardvark
Copy link
Contributor Author

I pushed an updated version of this PR which should work after @request/promise-core is updated. (The tests fail at the moment because the old version of @request/promise-core gets used.)

@analog-nico
Copy link
Member

Awesome, thank you!

@analog-nico analog-nico merged commit 445cdf6 into request:master Jul 30, 2016
@analog-nico
Copy link
Member

@not-an-aardvark in case you wondered: I just added a missing piece I forgot in @request/promise-core. Your tests are all green now.

@not-an-aardvark not-an-aardvark deleted the 113-cancellation-support branch July 30, 2016 20:46
@analog-nico
Copy link
Member

Hey guys, I just published version 4.1.0 which exposes the .cancel() method to properly cancel a request.

Thank you very much @not-an-aardvark for making this happen!

@nhducit
Copy link

nhducit commented Aug 1, 2016

thank you for your update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants