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

Make HTTP requests cancelable #74

Closed
surr-name opened this issue Jan 3, 2014 · 22 comments · Fixed by #124
Closed

Make HTTP requests cancelable #74

surr-name opened this issue Jan 3, 2014 · 22 comments · Fixed by #124
Assignees

Comments

@surr-name
Copy link

There is not presently a way to abort an outstanding HTTP request. This would require a mechanism for canceling a promise, which is not yet supported by Q.

@Stuk
Copy link
Collaborator

Stuk commented Jan 3, 2014

Afraid not, and looking at the current implementation I'm not sure how it would be supported. Promise cancellation is still up in the air afaik.

@Stuk
Copy link
Collaborator

Stuk commented Jan 3, 2014

This might not be what you want, but you can pass a promise into http.request, and if you reject it the request won't be sent.

@kriskowal
Copy link
Owner

I am renaming this issue, from “http.request.abort()” to “Make HTTP requests cancelable”.

@surr-name
Copy link
Author

Yes, actually, it was wrong question ( I've seen there is no way to abort request now =), the right one is — how it can be gracefully implemented?
Passing promise instead of requestObject is not a decision — I (and someone else) can want to abort request after it is started, for example for timeout reason, or even after the receiving of a data is started.
One of variants ( it is awful, i know ) can be to extend promise what is returning from http.request with abort method. But I'm sure there is better ways.

@kriskowal
Copy link
Owner

@surr-name That is approximately what would be necessary, except that we would have to support the promise cancel method at the Q level so it would not be a hack. That unfortunately implies a bunch of other things, see kriskowal/q#61.

@kriskowal
Copy link
Owner

Pardon, kriskowal/q#64

@surr-name
Copy link
Author

In my opinion it is not a global problem of promises, it is a local problem of applying promises to http.request. In any case, how one can access through canceler to node's http.request what is scoped in q-io/http.request to invoke abort(), setTimeout() or somethingElse() ?

@kriskowal
Copy link
Owner

Again, ideally, promise.timeout() would compose well by calling promise.cancel() if the timeout elapses. It does not. We could thread a "timeout" property in the request argument, e.g., request({url: ..., timeout: 5000}).

somethingElse is thankfully bounded by the extent of the Node.js request API surface, so we do not need to come up with an open-ended solution to accessing the underlying Node request object. We could however create an API that provided both the underlying and wrapped Node request objects, but it would be a different method.

@kriskowal
Copy link
Owner

Speaking with @erights, it seems that state-of-the-art for this kind of problem is to pass a promise as an optional argument. Rejecting that promise would be a signal that the subscriber would like the operation to be cancelled.

@kriskowal
Copy link
Owner

If we implement cancelability in Q-IO, we will accept a "canceled" promise as an argument. If we rebuilt something like Q-IO on top of https://github.com/kriskowal/gtor, we would use a Task instead of a Promise, and allow the user to coerce said task to a promise if they chose to attenuate cancelability.

@erights
Copy link

erights commented Sep 12, 2014

From the gtor document it seems you still agree about cancellation promises
-- that a cancellation promise goes in the opposite direction as the
results promise, and that it needs to be passed explicitly because it
composes differently across subgoals than do the results.

However, the end of that document suggests that until WeakRefs are
available, you will use Tasks instead of cancellation promises. I can see
how WeakRefs would be useful if we weren't separating the cancellation
promise from the result promise. But since we agree we should, why do
WeakRefs matter here?

On Thu, Sep 11, 2014 at 5:27 PM, Kris Kowal notifications@github.com
wrote:

If we implement cancelability in Q-IO, we will accept a "canceled" promise
as an argument. If we rebuilt something like Q-IO on top of
https://github.com/kriskowal/gtor, we would use a Task instead of a
Promise, and allow the user to coerce said task to a promise if they chose
to attenuate cancelability.


Reply to this email directly or view it on GitHub
#74 (comment).

Text by me above is hereby placed in the public domain

Cheers,
--MarkM

@kriskowal
Copy link
Owner

I am academically interested in exploring the paradigm of Tasks and Promises side-by-side with measured understanding of the limitations of both (GToR) but pragmatically committed to using promises alone (Q, Q-IO, Q-Connection, id est Q*, ECMAScript).

@erights
Copy link

erights commented Sep 12, 2014

That's fine of course. But what is your point about WeakRefs? This answer
still me confused.

On Thu, Sep 11, 2014 at 8:15 PM, Kris Kowal notifications@github.com
wrote:

I am academically interested in exploring the paradigm of Tasks and
Promises side-by-side with measured understanding of the limitations of
both (GToR) but pragmatically committed to using promises alone (Q, Q-IO,
Q-Connection, id est Q*, ECMAScript).


Reply to this email directly or view it on GitHub
#74 (comment).

Text by me above is hereby placed in the public domain

Cheers,
--MarkM

@surr-name
Copy link
Author

I really can't imagine a place of cancelability at Promise's paradigm. Can you draft your vision of it? there is a lot of questions about it:

  • what is state of canceled promise
  • should there to be a new branch "canceled" at the level of fulfilled/rejected
  • what if there are more than one listener
  • what should be a behavior when invoking reject or resolve methods of Deferred, when its Promise is canceled
  • what if new listener appears after a promise is canceled

And again, don't you think that aborting HTTP requests is not a problem at Promise's level?

May be it can to be a decision to add possibility to create a special object of additional methods for particular promise at deferred's level?

@surr-name
Copy link
Author

An example to illustrate my last paragraph:

function getPromise () {

    var D = Q.defer();

    // ...

    D.promise._extra = {
        doSomeExtraAction : function () {

            // do something, for example:

            D.reject();

            // or abort an HTTP request,
            // or anything else
        }
    };

    return D.promise;
}


var promise = getPromise();


promise
.then(function(){
    // ...
})
.fail(function(){
    // ...
});


setTimeout(function(){

    if ( something ) promise._extra.doSomeExtraAction();

}, 5000);

@surr-name
Copy link
Author

Sorry, if it is wrong place for discussing cancelable promises, but don't you think this way has no direct collisions with Promise A+ spec, unlike cancelablement has? It can resolve the problem with aborting HTTP requests, and a lot of others.

@kriskowal
Copy link
Owner

@surr-name, sorry for the distraction about cancelable promises. Exposing cancelation was my intuition for the right API for the feature you are suggesting. I am suggesting now that this intuition had fundamental flaws and we will not explore that direction here.

There is a one way communication channel from a resolver to a promise. To abort an HTTP request is to communicate in the other direction, from the consumer to the producer. Mark Miller is suggesting that we establish an explicit channel in that direction, giving the consumer a resolver and the producer retaining a promise. This is slightly different than your suggestion of using a deferred, but in the same spirit.

The interface might look like:

var cancellation = Q.defer();
var responsePromise = HTTP.request({url, method, cancelled: cancellation.promise});
// To communicate impatience or disinterest:
cancellation.reject(new Error("Client no longer interested in HTTP response");

Q-IO’s HTTP module would observe the cancellation promise if one is provided and abort if it settles. Mark had some suggestions about fail-only promises which we might explore, but in principle, the cancellation promise should only be rejected and that rejection would propagate back to the response promise.

And this would be a pattern that we would expose throughout Q-IO for cancellation in general.

I will leave the academic discussion of the alternative approach of using Task to the GToR project and discourage continuing that discussion here.

@surr-name
Copy link
Author

sounds good, but
there can be scenarios where it is not enough, the way with extra object looks more flexible, for me.

Let me illustrate it with stupid example:

function giveMeAnApple () {

    var D = Q.defer();

    // ...

    // ok, I've no apples for the moment,
    // but I'll try to buy one for you
    var promiseForAnApple = D.promise;


    // It'll be red,
    var color = 'red';


    // but untill I've bought it,
    // you can say wich color you prefer
    promiseForAnApple._extra = {
        iWantColor : function ( preferedColor ) {

            if ( promiseForAnApple.isPending ) color = preferedColor;
        }
    };


    return promiseForAnApple;
}


// ...


var promise = giveMeAnApple();


promise
.then(transferAppleToChild, cryAboutInjustice);


askChildForColor()
.then(promise._extra.iWantColor);

Do you see any weakness of this pattern?

@surr-name
Copy link
Author

The example is really stupid and says nothing about HTTP request, but shows a flexibility.

What about the requests:
the cancellation pattern does not answers for multiply listeners question. If cancellation.promise is just propagated, what if others listeners still awaiting for a response?

@kriskowal
Copy link
Owner

Although I do not understand what this example illustrates, I can say without hesitation that we will do nothing that depends on promises being mutable. Promises are designed specifically such that they can be frozen and distributed to mutually suspicious consumers without introducing a covert communication channel or opportunity to interfere between them.

@surr-name
Copy link
Author

You're right about the Promises, but the way doesn't affects Promises, it is beside, it is about wide communication channel between a consumer and a producer of some task.

cancellation promise pattern solves the problem, but is synthetic, in my opinion. In most cases the promise is always left in pending state, and has no sense in resolved state.

@kriskowal
Copy link
Owner

See #124 for resolution.

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 a pull request may close this issue.

4 participants