Skip to content

Instantly share code, notes, and snippets.

@NekR
Last active August 29, 2015 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NekR/abccd8e8d0b9b8a780a3 to your computer and use it in GitHub Desktop.
Save NekR/abccd8e8d0b9b8a780a3 to your computer and use it in GitHub Desktop.
Promises with |end| capability

#Promises with |end| capability

Maybe it's better to think about cancellation as a finishing? Actually just use finishing as cancellation for promises which do not just store data, but rather makes some request.

This is not a secret what browsers do some tricky thing to determine if promise chain ended so Promise and its value might by GCed. It also does not work for stored promises like window.p = new Promise(...). So promises needs not only cancellation, but finishing ability too. It might be combined.

Let's for example introduce .end() (just example of name) method on the Promise.prototype which by default lock current promise and tells to GC what no one can anymore access value of this promise. It's just becomes no-op completely (all the methods) so GC might collects its value or stop referencing it with promise. Potentially, this also should work with chain of the promise, so once .end() called it also tries to end chain. Of course it should use ref-counts, etc. Few examples:

Ex. 1

var pending = new Promise(function(resolve) {
  setTimeout(resolve, 1000);
});

var a = pending.then(function() {
  console.log('a')
});

var b = pending.then(function() {
  console.log('b')
});

// This lock promise, all methods (then, catch, end) becomes no-op.
// Ff promises was not settled before lock (as per this case) it also
// makes executor functions no-op (fullfill/reject) and locks all chained promises
// so a and b handlers will never be called.
// But if promise was already settled it does nothing for previous chained promises,
// only makes methods no-op (then, catch, end)
pending.end();

Ex.2

var pending = new Promise(function(resolve) {
  setTimeout(resolve, 1000);
});

var a = pending.then(function() {
  console.log('a')
});

var b = pending.then(function() {
  console.log('b')
});

// this locks promise |b| in same way as |pending| promise in prev. example
// and also tried to lock |pending| promise, but since it has more than last consumers, this attempt fails
// here |b| handler never will be called
b.end();

// now we lock |a| promise, this locks |a| itself and also locks |pending| since |a|
// is the last consumer of the |pending|
a.end();

Ex. 3, with fetch

var doFetch = function(...) {
  var req = fetch(...);

 var result = req.then(function(res) {
    return res.copy().json();
  });

 return result;
}

var pending = doFetch(...);

// this locks |pending| promise which is same as |result| promise
// in |doFetch|, since |result| is only consumer of |req| promise,
// its try to lock will success and |req| will receive internal signal to stop
pending.end();

Ex. 4, with fetch

var doFetch = function(...) {
  var req = fetch(...);

 var result = req.then(function(res) {
    return res.copy().json();
  });

 req.then(function() {
  // Here |result| is still in pending state, but |req|
  // is settled so |result| now stores |res.copy().json()| promise as its value
  // Locking |resulse| now will make it no-op and also will try to lock
  // |res.copy().json()| and since there is no other consumers for it,
  // action will success and Stream responsible for |res.copy().json()| will be
  // notified to end
  result.end();
 });

 return result;
}

var pending = doFetch(...);

Of course, fetch will should have its internal handler for lock signal to abort actual network activity. But I do not think ability to override .end() action should be provided in promise executor (new Promise(function() { /* here */ })), rather I think it should be provided per-class. So, in ES7 (because of .end()) one can extend Promise and override end() action by @@somthing. This will keep promises in their "private nature", but also will provide overridable end() action for subclass, such as FetchPromise.

@NekR
Copy link
Author

NekR commented Mar 31, 2015

This is rough proposal which does not cover all cases (yet), but I will work on it.

@WebReflection
Copy link

you are not providing anywhere the ability to create de-facto "endable" promises.

Dos this mean every Promise function should be executed in a way that all possible asynchronous things like a setTimeout call should automagically "die" if p.end() is called?

I don't see how this could possibly work ... maybe it does in wizardland native/core, but it's a footgun for users since these won't have any ability to cancel their own code (clearInterval or anything else that could be involved)

I also feel here there's nothing new to what I've solved already ... beside the .end() naming convention.

What am I missing?

@NekR
Copy link
Author

NekR commented Mar 31, 2015

you are not providing anywhere the ability to create de-facto "endable" promises.

I believe all promises should be endable de-facto. And default behavior for it should be to forget its data, if possible of course.

About setTimeout, no, they should not die. In that case setTimeout will call resolve function, but it will be no-op.

If someone will really want some cancel/cleanup capability then one may extend Promise to AjaxPromise or TimeoutPromise and return it via ajax(...) or timeout(...).

I also feel here there's nothing new to what I've solved already ... beside the .end() naming convention.

May be, but idea is to work in a different way. Not "cancel-ability", but ending/locking promise which affects primary GC and values stored in Promise and secondary provides ability to implement "cancellation" via such API.

Cancellation is powerful things, so, if some one will need, then they may create helper which returns instance of required promise with required abilities, instead of doing that stuff in executor. This exactly prevents "footgun" thing.

@getify
Copy link

getify commented Mar 31, 2015

Just want to state for posterity sake that this approach violates the principle of external-immutability of a promise, and thus enables the "action at a distance" sin of system design. No one seems to take that seriously, but it's a big important detail. I would object strenuously, if my vote carried any weight anyway, to bastardizing promises in the way suggested here.

@NekR
Copy link
Author

NekR commented Mar 31, 2015

@getify

Same is applicable for cancellation, or you are against it too?

@WebReflection
Copy link

About setTimeout, no, they should not die. In that case setTimeout will call resolve function, but it will be no-op.

Yeah, that's your use case ... now try to imagine that is not what anyone would ever write for real since quite pointless ;-)

The point here is that I want to do a JSONP request via Promise and if canceled because it's taking forever or for any other reason I want to drop the request somehow.

Your .end() idea does not give me the ability to react and actually terminate my own code the way I need in order to really end that promise.

If there's no way to remove listeners, clear timers, drop requests, drop workers, drop the hell I want to, from user-land, your proposal works only natively and as such I would -1 it, beside the important points also already mentioned by Kyle.

@WebReflection
Copy link

Same is applicable for cancellation, or you are against it too?

the way it's going "in the other room" is that cancel ends up rejecting so nothing new and no concrete changes in Promises-land (beside exposing rejection, which is the reason I voted and implemented in the first case a canceled state example ... which is way more complex to rethink as specification and probably makes sense to be simply replaced by Tasks)

@NekR
Copy link
Author

NekR commented Mar 31, 2015

the way it's going "in the other room" is that cancel ends up rejecting so nothing new and no concrete changes in Promises-land

So it still "violates the principle of external-immutability" or am I wrong? Any method added to promise which might externally fullfill/reject/cancel it violates that principle so your proposal has same problem as mine and you can apply his words for your implementation too.

Kyle is absolutely right about it, is not in nature of Promises and I am too against it. But, it all seems what fetch will use Promises and nothing else and it needs cancellation so you and I both proposing bad patterns, which looks ugly as I said in "Why so fetch?".

your proposal works only natively and as such I would -1 it

I believe in ES6 anyone can extend any built-in class and override Symbol properties. Am I wrong here? If not, then anyone can promise with cancellation capabilities. Just not in the same was as in your proposal.

Just to be clear, I am not saying your proposal wrong, it might work for just cancellation situation, but goal in this proposal to also solve promises chain ending.

@WebReflection
Copy link

you wrote the word Symbol only now ... no, I didn't realize you were talking about lock Symbol and how that would have worked. You should stick your example in here, from the other room.

About violated principles, that's the reason I wrote my blogpost too about Fetch and the fact it was a very unfortunate choice to adopt a non cancellable pattern for a network related API, but also I'm off philosophy at this point because somebody did a mistake that for some reason cannot be un-done and same way I can manually expose resolve and reject through Promises, so that the initial design should have been probably more guarded in this regard, I can expose what I want when I need so that it is possible, only when I provide a bad API pattern as a cancellable Promise, a way to abort/cancel/drop whatever that is, and eventually reject.

Fetch and granted immutability are in conflict, so let's play for the infamous case in a way that's simple to configure, simple to understand, and does not violate any principle of all other surrunding Promises: hence reject is reasonable, exit is not (exit is what I've proposed already as cancellable in my first playground in the other thread but nobody could even understand what was going on, or what was the idea ... until I've realized Tasks are a better fit for Fetch and cancelable Promises)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment