tc39 / proposal-cancelable-promises

Former home of the now-withdrawn cancelable promises proposal for JavaScript
Other
376 stars 29 forks source link

Idea: cancelability in base for producer; cancelability in a subclass for consumers #2

Closed domenic closed 8 years ago

domenic commented 9 years ago

The premise so far is that there are three states:

Each of these propagate downstream, so that .then(undefined, undefined, undefined) will produce a new promise in the exact same state (and with the same value/reason, if applicable).

As such, I propose the following:

The canceled state is part of the base Promise

In particular, the constructor signature becomes

new Promise((resolve, reject, cancel) => { ... });

If the executor function uses the cancel() function to put the promise in the canceled state, then only onFinally handlers trigger.

Note that like resolve and reject, it is a no-op to call cancel if the promise is not in a pending state.

This allows the creation of promises which can only be canceled by their creator, but otherwise behave like you would expected a cancelable promise to behave. In particular, their canceled-ness propagates "downward" to any descendants.

Note: we could add a static Promise.cancel() to parallel Promise.resolve(v) and Promise.reject(r), but that seems kind of pointless since canceled promises don't have any value or reason to make them interesting.

The CancelablePromise subclass is a convenience for exposing cancelation to the consumer

The CancelablePromise subclass allows the addition of custom cancelation actions, besides just changing the state. And it adds a .cancel() method, which deals with refcounting, so that once the refcount reaches zero, it triggers the custom cancelation action and then changes the state.

Furthermore, the .then() of CancelablePromises ensures that it increases the refcount, and returns a CancelablePromise whose cancelation action is to call parent.cancel(). This is all as already planned.

This ensures combinators work more correctly

Consider:

// let c1 and c2 be CancelablePromises
const p = Promise.all([c1, c2]);

c1.cancel();
c2.cancel();

// we would like p to be "canceled", i.e. only trigger onFinally handlers.

This should work. Right now, Promise.all does:

We then add:

This doesn't solve all combinator problems; libraries using Promise.all instead of CancelablePromise.all have thrown away the ability to cancel the return value, after all.

jakearchibald commented 9 years ago

How does this related to the cancel callback?

new Promise((resolve, reject, cancel) => {
  cancel();
  return () => console.log(1);
});

new CancelablePromise((resolve, reject, cancel) => {
  cancel();
  return () => console.log(2);
});
jakearchibald commented 9 years ago

As of bdd175890e0099d61afe72ddba9a582741bd989f, regular promises are cancelable by the creator.

Here are my current answers for the above, but I'm not set on it:

Does cancel() trigger the cancelation callback?

No

Does cancel() queue a microtask to trigger the cancelation callback?

No

Does the Promise constructor accept a cancelation callback, or is it unique to CancelablePromise?

Unique to CancelablePromise.

jakearchibald commented 9 years ago

For what it's worth, having cancel as part of regular promises made everything a lot easier. Good call.

domenic commented 9 years ago

Sorry, in Tokyo this week, so time zones are a bit off... I agree with all your answers! Will check out the commit, but glad that this gave a nice simplification, as that's exactly what I was hoping for.

domenic commented 8 years ago

Not doing tasks.