
Cancelable Promises: Why was this proposal withdrawn? - xatxat
https://github.com/tc39/proposal-cancelable-promises/issues/70
======
cwmma
Promises have always been extremely contentious in JS for a couple reasons and
Domenic has had the patience of a saint getting the original proposal through
so it's totally understandable that he might not want to deal with this
anymore especially if people in his org are against it.

There were a couple reasons the original promises were so contentious to
standardize.

\- Monads: there was a very vocal faction on TC-39 that really wanted a
Promises to be monadic and very much emphasized mathematical purity over
usefulness and any attempt to discuss things tended to get side tracked into a
discussion about monads, see brouhaha over Promise.cast [1].

\- Error handling: Promises have a bit of a footgun where by default they can
swallow errors unless you explicitly add a listener for them. As you can
theoretically add an error listener latter on all other options had downsides
(not always obvious to advocates) leading to a lot of arguing, must of it
after things had shipped and realistically couldn't be changed. Fixed by
platforms and libraries agreeing on a standardization of uncaught promise
events [2].

\- Just an absurd amount of bike shedding probably the 2nd most bike shedded
feature of the spec (behind modules).

So cancelable promises reignite all the old debates plus there's no obvious
right way to do it, like should it be a 3rd state which could cause
compatibility issues and would add complexity, or should cancellations just be
a sort of forced rejection, which would be a lot more backwards compatibility
but with less features.

Additionally there is a bizarre meme that somehow observables (think event
emitters restricted to a single event or synchronous streams that don't handle
back pressure) are a replacement for promises or are a better fit for async
tasks they should be used instead.

edit: patients => patience

1\. [https://esdiscuss.org/topic/promise-cast-and-promise-
resolve](https://esdiscuss.org/topic/promise-cast-and-promise-resolve)

2\.
[https://gist.github.com/benjamingr/0237932cee84712951a2](https://gist.github.com/benjamingr/0237932cee84712951a2)

~~~
bad_user
> _Monads: there was a very vocal faction on TC-39 that really wanted a
> Promises to be monadic and very much emphasized mathematical purity over
> usefulness and any attempt to discuss things tended to get side tracked into
> a discussion about monads, see brouhaha over Promise.cast_

Monads are about usefulness, because a type being monadic means there is a
bind/flatMap operation you can rely on to have a set of laws. So a Promise
being Monadic means that it is _lawful_ and more _composable_. And it is
especially relevant in the context of a Future/Promise. Aren't you tired of
Javascript Future/Promise implementations that are broken?

Like, please learn about the concept.

~~~
depr
I don't get the value of composability and refuse to learn, and "monad" and
"laws" sound like math which I don't want to understand. So instead of
attempting to comprehend what this is about I'm just going to make fun of you
in a comment.

~~~
throwanem
I don't see a lot of point in making better programming concepts seem
accessible, and "try to meet people where they are" sounds like populism which
I don't want to understand. So instead of attempting to argue that there's
value in the idea in a way that doesn't put people off with an implicit
accusation of lazy ignorance, I'm just going to go with the implicit
accusation of lazy ignorance.

------
wodencafe
It sounds like some Google employees on TC-39 had significant resistance to
the idea, and it is worrisome that employees of a private company can block
proposals in favor of "their" way, in what is supposed to be an open and
transparent Technical Committee, for a language that is supposed to be for all
of us.

Google trying to strong-arm control of JS for themselves.

~~~
jpalomaki
From the Github comments:

"One of the important priorities in TC39 is to achieve consensus - which means
that any employee of any member company can always block any proposal. It's
far far better for anyone to be able to block, than to arrive at a spec that
"not everyone" is willing to implement - this is, by a large large margin, the
lesser evil, I assure you."

~~~
mcguire
This is why you don't do development by standards committees. It never ends
well.

~~~
BrendanEich
You miss the point that TC39 uses a champions model: 1 or 2 people who do
design, with the committee reviewing.

That broke down here, to the extent that the champion, Domenic, withdrew under
some kind of internal (to Google if not his own state of mind) duress. TC39
never had a chance to consider the argument or whatever it was that caused
this.

More to say after January's meeting, I'm sure. I'm not going to throw stones
at Google but it would not surprise me if there were backroom objections. If
so, they should be brought to the committee cleanly. There is still time.

~~~
mcguire
Do the champions provide an implementation of the proposal for the committee
to review?

~~~
BrendanEich
See [https://tc39.github.io/process-document/](https://tc39.github.io/process-
document/). To get real engine implementations may take cooperation among
champion and one or two browser vendors, as the "real engines" (meaning ones
that can be tested against the Web at scale) are all owned by browser vendors.

Examples:

* SIMD started from Dart's proposal, which John McCutchan then championed at first for JS, and later handed off to others. Intel, Mozilla, and Google all cooperated.

* ES6 Proxies, where Mark Miller and Tom Van Cutsem were the champions and did a terrific job covering the design space and finding the sweet spots. Andreas Gal implemented in SpiderMonkey and then the spec changed, based on the implementation (but in a mostly co-expressive way, for wins beyond expressiveness; the new proxy implementation layered on the internals of the old). Proxies were quite challenging as they exposed observable internal order of operation details and required reformulating spec-mandated invariants that matter for not only interoperation but also security.

------
dreeko_
promise for cancelable promises has been cancelled, how poetic

~~~
paradite
And the author unsubscribed from the subject.

Context: [https://github.com/tc39/proposal-
observable/pull/97](https://github.com/tc39/proposal-observable/pull/97)

------
wycats
TC39 member here.

cwmma gets a lot right here: Promises have always been contentious in JS (and
TC39) and Domenic has indeed had the patience of a saint attempting to wrangle
the various points of view into a coherent proposal.

TC39 as a group is generally very motivated to find positive-sum outcomes and
find solutions that address everyone's constraints in a satisfactory way. That
doesn't usually mean design-by-committee: champions work on a coherent design
that they feel hangs together, and the committee provides feedback on
constraints, not solutions.

As a member of TC39, I'm usually representing ergonomic concerns and the
small-company JavaScript developer perspective. I've had a lot of luck, over
the years, in giving champions my perspective and letting them come back with
an improved proposal.

The staging process (which I started sketching out on my blog[1]) has made the
back-and-forth easier, which each stage representing further consensus that
the constraints of individual members have been incorporated.

Unfortunately, I fear that promise cancellation may be a rare design problem
with some zero-sum questions.

It's worth noting that there has been no objection, on the committee, to
adding cancellation to the spec in some form.

The key questions have been:

First. Is cancellation a normal rejection (a regular exception, like from
`throw`) or a new kind of abrupt completion (which `finally` would see but not
`catch`). The current status quo on the committee, I believe, is that multiple
people would have liked to see "third-state" (as Domenic called it) work, but
the compatibility issues with it appear fatal.

Second. Should promises themselves be cancelled (`promise.cancel()`) or should
there be some kind of capability token threaded through promises.

What that would look like:

    
    
        let [sendCancel, recvCancel] = CancelToken.pair();
        fetchPerson(person.id, recvCancel);
    
        async function fetchPerson(id, token) {
          // assume fetch is retrofitted with cancel token support
          let person = await fetch(`/users/${id}`, { token });
        }
    
        // when the cancel button is clicked, cancel the fetch
        cancelButton.onclick = sendCancel;
    

This approach had many supporters in the committee, largely because a number
of committee members have rejected the idea of `promise.cancel()` (in part
because of ideological reasons about giving promise consumers the power to
affect other promise consumers, in part because of a problem[2] Domenic raised
early about the timing of cancellation, and in part because C# uses cancel
tokens[3]).

In practice, this would mean that intermediate async functions would need to
thread through cancel tokens, which is something that bothered me a lot.

For example, it would have this affect on Ember, if we wanted to adopt cancel
tokens:

    
    
        // routes/person.js
    
        export default class extends Route {
          async model(id, token) {
            return fetch(`/person/id`, { token });
          }
        }
    

In other words, any async hook (or callback) would need to manually thread
tokens through. In Ember, we'd like to be able to cancel async tasks that were
initiated for a previous screen or for a part of the screen that the user has
navigated away from.

In this case, if the user forgot to take the cancel token (which would likely
happen all the time in practice), we would simply have no way to cancel the
ongoing async.

We noticed this problem when designing ember-concurrency[4] (by the venerable
Alex Matchneer), and chose to use generators instead, which are more flexible
than async functions, and can be cancelled from the outside.

At last week's Ember Face to Face, we discussed this problem, and decided that
the ergonomic problems with using cancel tokens in hooks were sufficiently bad
that we are unlikely to use async functions for Ember hooks if cancellation
requires manually propagating cancel tokens. Instead, we'd do this:

    
    
        // routes/person.js
    
        export default class extends Route {
          *model(id) {
            return fetch(`/person/id`);
          }
        }
    

The `*` is a little more cryptic, but it's actually shorter than `async`, and
doesn't require people to thread cancel token through APIs.

Also notable: because JavaScript doesn't have overloading (unlike C#), it is
difficult to establish a convention for where to put the cancel token ("last
parameter", vs. "the name `token` in the last parameter as an options bag" vs.
"first parameter"). Because cancellation needs to be retrofitted onto a number
of existing promise-producing APIs, no one solution works. This makes creating
general purpose libraries that work with "promise-producing functions that can
be cancelled" almost impossible.

The last bit (since I started talking about Ember) is my personal opinion on
cancel tokens. On the flip side, a number of people on the committee have a
very strongly held belief that cancel tokens are the only way to avoid leaking
powerful capabilities to promise consumers.

A third option, making a new Task subclass of Promise that would have added
cancellation capabilities, was rejected early on the grounds that it would
bifurcate the ecosystem and just mean that everyone had to use Task instead of
Promise. I personally think we rejected that option too early. It may be the
case that Task is the right general-purpose answer, but people with concerns
about leaking capabilities to multiple consumers should cast their Tasks to
Promises before passing them around.

As I said, I think this may be a (very, very) rare case where a positive-sum
outcome is impossible, and where we need, as a committee, to discuss what
options are available that would minimize the costs of making a particular
decision. Unfortunately, we're not there yet.

Domenic has done a great job herding the perspective cats here, and I found
his presentations on this topic always enlightening. I hope the committee can
be honest enough about the competing goals in the problem of cancellation so
that Domenic will feel comfortable participating again on this topic.

[1]: [https://thefeedbackloop.xyz/tc39-a-process-sketch-
stages-0-a...](https://thefeedbackloop.xyz/tc39-a-process-sketch-
stages-0-and-1/)

[2]: [https://github.com/tc39/proposal-cancelable-
promises/issues/...](https://github.com/tc39/proposal-cancelable-
promises/issues/8)

[3]: [https://msdn.microsoft.com/en-
us/library/dd997289(v=vs.110)....](https://msdn.microsoft.com/en-
us/library/dd997289\(v=vs.110\).aspx)

[4]: [http://ember-concurrency.com/#/docs](http://ember-
concurrency.com/#/docs)

~~~
amitzur
Thank you! This is helpful. Do you believe there is no positive sum because
Promises are out there already, and this could have been avoided had they been
released with cancellation in the first place?

~~~
wycats
I think it's very likely that we could avoid turning this into a zero-sum
debate with one total-winner and one total-loser.

That said, I think there's no positive sum because some folks believe that (1)
Promises should be the primary async story in JS, and (2) Promises must not
allow communication between two parties who hold a reference to the Promise.
This means that `async function`s must return a Promise, and the Promise but
not have a `cancel()` method on it (because it would allow communication
between two parties holding references to the Promise).

Others (I'll speak for myself here) believe that the return value of `async
function` should be competitive (ergonomically) with generators used as tasks
(I showed examples in the parent comment). Since generators-as-tasks can be
cancelled (via `.return()` and `.throw()`), the desire to make `async function
x` as ergonomic as `function* x` conflicts with the goal of disallowing the
return value of async functions from being cancellable.

In Ember's case, since generators _already exist_ , it's hard for us to
justify asking application developers to participate in an error-prone (and
verbose) protocol that we could avoid by using generators-as-tasks instead.
And that is likely the conclusion that we will ship (and the conclusion that
ember-concurrency has already shipped).

For _me_ , the bottom line is that we have very little budget to introduce new
restrictions on `async functions`, because people can always choose to reject
async functions and use generators instead (with more capabilities and _less_
typing!). I think the cancellation token proposal is well over-budget from
that perspective.

------
antirez
After something like that I would leave the company, probably. If something
you care so deeply encounters a very strong internal opposition, probably
there is an unaligned design idea and is better to provide efforts where those
are better accepted, because otherwise the same story is very likely to
repeat. And you know... life is too short.

~~~
zcdziura
I don't think this situation would be worth quitting one's job over. So you
didn't get your way after arguing with your peers over it, it happens. As you
say, life is short.

~~~
mcguire
Depends. You may end up being "that guy with the stupid idea" for the rest of
your time there.

~~~
Retra
Only if that's the only idea you ever have.

------
dchest
Discussion on Twitter:
[https://twitter.com/andrestaltz/status/809521268332593152](https://twitter.com/andrestaltz/status/809521268332593152)

------
batmansmk
Still, the debate doesn't happen publicly. I don't know why and who opposes
it. Google has no position in it. But someone undisclosed with sufficient
power at Google opposes it. And does'nt share his point of view with the
community...Sad day for open source.

~~~
BinaryIdiot
It's typically written up in meeting notes and someone publishes them if it
happens during a meeting. If it happens outside of a meeting it likely
happened in GitHub or the ECMAScript mailing list.

The person(s) opposing have likely already shared their view point with the
community at different stages. Proposals are not simply created and championed
with zero feedback from the ECMAScript folks util they're proposed at a
meeting.

------
leothekim
Serious question - why is cancelling a promise a reasonable use case? Doesn't
cancelling potentially invite non-deterministic state into your program? If
not, what would be the difference from throwing?

~~~
daleharvey
I have been waiting for cancellable promises for a while primarily for
cancelling long lasting fetch requests which is a very common thing to need.

(Not really sure how to parse non determinism and throwing being related to
cancelling promises)

~~~
fareesh
What's a good existing way to deal with this use-case? Is there a useful
library that wraps setTimeout / other logic to determine the quality of a
user's internet connection in case of very poor connectivity (2G etc) ?

~~~
jdesjean
The way to handle this case is to use Promise.race with 2 promises. The first
promise is your logic. The second promise is rejected after a timeout. For
more detail read the section "never calling the callback" from "you don't know
js" book on "async" in chapter 3 [https://github.com/getify/You-Dont-Know-
JS/blob/master/async...](https://github.com/getify/You-Dont-Know-
JS/blob/master/async%20%26%20performance/ch3.md)

~~~
soft_dev_person
But that doesn't actually cancel the request, and thusly the data is
downloaded anyway? Or am I missing something?

------
taneq
I'm not really familiar with the backstory here but it sounds like domenic was
being seriously pressured/coerced to drop the proposal. What happened?

~~~
StavrosK
It sounds to me like most of the other people were disagreeing with him.

~~~
mikeryan
Can't tell if "most" or "small group - but enough" people.

~~~
taneq
"vocal minority"

------
k__
Funny thing is, they didn't stop it because it was Bad, but because some ppl
at Google didn't want it.

(probably because it was Bad, but it's sad to See that one company has so mich
power)

~~~
cloverich
Based on the comments in the linked thread, its sounds as if all participating
companies have that same veto power. The justification seems to favor
filtering false positives over false negatives in a sense. I suppose I can
agree with that.

~~~
k__
Yes.

Getting crap into the language is worse than not getting good stuff in.

But did they really veto here?

As I read a few days ago, the guy doing the proposal just was forbidden by
Google to work on it anymore.

I wasn't in favor of this proposal think it's one of the better outcomes, but
the form how if came to be dropped was a bit strange.

------
codedokode
Cancellation can be implemented with another promise passed into cancelable
function as an argument. This way it is easier to see that asynchronous
function is cancellable. And Promise implementation would become simpler.

------
nraynaud
was it an actual mid-computation cancellation with collaboration from the VM?

I have done some heavy computations in JS, and it's a nightmare: your workers
can be killed (I guess it's the browser saving resources) without any even
escaping them, and webGL is always blocking the main thread (I had to chunk my
computation myself).

~~~
explorigin
No it's for I/O ops. Think cancelling a fetch() like you can do with XHR.

~~~
nraynaud
ah! that seem like the easy stuff.

~~~
ralfn
But fetch() returns a promise, rather than a handler you can use to cancel the
request. So some people wanted to add "cancel" as a third potential state of a
promise.

Which smells a bit like a hack to me. Why stop at cancel? The Fetch API
shouldnt return a promise, maybe?

But im assuming a lot of people gave it a lot of though. Maybe it is common
enough. Maybe it is horrible to implement in a performant way.

But so far the new ES features ive seen should have been critisized more not
less. Look at the ugly mistakes that did make it:

    
    
        f => { pizza }
    

Is the block on the right a statement block or an object literal? Its a
statement block so if your lambda function returns an object literal than you
need to wrap it in a statement block with a return statement. If it is
anything else, than you dont.

Thats the horrible crap that actually gets through the proccess. Imagine how
naive and broken the propositions must be that get killed?

~~~
EvilTerran
> if your lambda function returns an object literal than you need to wrap it
> in a statement block with a return statement

Or you can just parenthesise the object literal:

    
    
        >>> (x => ({ x }))(23)
        Object { x=23 }
    

The grammatical ambiguity is unfortunate, sure, but the simplest work-around
isn't so bad.

~~~
ralfn
If one forgets the (parentheses) you may or may not immediate or later can an
error _somewhere_.

It isn't about the workaround -- it's about all the subtle bugs it will
introduce in the edge-cases, for example:

    
    
         f => () => {}
         f(); // undefined instead of empty object

------
trungaczne
I suppose this is related?

[https://github.com/tc39/proposal-
observable/pull/97](https://github.com/tc39/proposal-observable/pull/97)

------
Raed667
I'm trying to understand why Google people don't want cancellable promises?

Did I miss an explanation somewhere?

~~~
pitaj
They probably want some form of cancellable promises, but think that the
semantics and syntax in this proposal were bad. I agree with that.

------
kbody
Cases like these show how needed transparency is (see Mozilla). Hopefully this
will cause Google to open up some more (although doubt it).

~~~
Klathmon
They are being transparent, there is a scheduled meeting of everyone involved
in january.

And using this comment to rant a bit, I hate the idea that just because
information isn't being announced immediately after someone finds out about
something means that you aren't being "transparent".

In this case, this was most likely discussed internally, and come jan they
will present the results on why it was canceled giving insight into the why
and perhaps it's replacement or their ideas moving forward.

Now if come january it's just said "it's canceled" and nothing more, then i'll
be right there with you calling for transparency and more discussion on the
reasons, but you need to give the people involved a chance to actually have
that discussion.

~~~
spangry
I have a different view on 'transparency': something is 'transparent' if you
can see the internals of that thing at any given moment. Time is a critical
factor for transparency: What if, instead of the next meeting being in a month
(where we hope this will be discussed), it was in a year? 2 years? 10?

People have finite attention spans. There's probably a well-intentioned,
innocuous explanation as to why the process has been set up like this: I
imagine the time delay is intended to give people time to consider the issue
(reducing the chances of irrational emotional outbursts, increasing the
chances of rational discussion).

However, introducing a delay between 'decision' and 'revelation' of the
internal machinations behind the decision creates greater scope for people to
act in bad faith. This seems at odds with what (I think) is the function of
transparency.

Whether this is an acceptable trade-off is a separate issue.

------
novaleaf
I use Promises extensively, including cancellations (via the bluebird library)
and find the following to be strong reasons not to ever use them, if they are
to be implemented in a similar way that Bluebird uses them:

1) forward propagation side effects: canceling a promise doesn't mean
rejecting. it means that further .then() or catch clauses are not invoked.
This breaks (imo) a fundamental tenant of promises.

2) backwards propagation side effects: if you are waiting on a promise and
cancel it, this is causing the previous promises in the chain to suddenly stop
processing, again without any rejection, it just stops.

3) simple workaround: I don't think it is appropriate to backwards propagate
promise cancellation, but it is very easy to emulate cancellation for
downstream users (those using your promise). simply attach a
"pleaseAbort=false" property to your promise and if a downstream caller wishes
to cancel, they set .pleaseAbort=true and you can stop your processing and
return a rejected promise.

------
dcarmo
It would've certainly helped to avoid this:
[https://facebook.github.io/react/blog/2015/12/16/ismounted-a...](https://facebook.github.io/react/blog/2015/12/16/ismounted-
antipattern.html)

------
BigJono
Can someone give a good use case for these that can't be easily solved with
existing tools? I'm really struggling to find a good example. I seem to have
this problem for a lot of new stuff introduced into Javascript...

~~~
idbehold
Unlike an XMLHttpRequest which can be aborted, the new fetch() API does not
provide a way for you to abort the request. This is because there is no
standard way to cancel a Promise which is what fetch() returns.

~~~
couchand
So.... we introduced Promises so that we could introduce fetch() that returns
a Promise, only to discover that it meant we couldn't get all the other useful
behavior of XMLHttpRequest. So while a proposal to add cancelable Promises
would fix the inability to abort, we'd still need yet more additions to
completely get back to functionality we had previously.

~~~
idbehold
That wasn't the reason promises were introduced. An XMLHttpRequest can be
synchronous which is Bad™ and that is something explicitly not allowed by the
Promise/A+ spec which means it will never be something that fetch() could do.
The advantage of promises is that they can be `await`ed in the context of an
`async` function which takes a lot of the cognitive load off of the developer.

~~~
Klathmon
Furthermore the `await` syntax is one of the reasons why cancelable promises
is contentious.

There's no easy way to "cancel" an awaited promise without basically resorting
to the .then() syntax.

------
strictfp
Sounds like MIT vs Worse is Better

------
semi-extrinsic
For anyone else left clueless by title: a proposed new feature for JavaScript,
as in ECMA TC39, was "cancellable promises", trying to handle "cancellation"
of async actions ("promises") by extending normal try-catch structures with a
"canceled" case. Due to unspecified internal Google drama, this (presumably
popular) proposal has been withdrawn.

Here are some slides with technical details of the proposal:

[https://docs.google.com/presentation/d/1V4vmC54gJkwAss1nfEt9...](https://docs.google.com/presentation/d/1V4vmC54gJkwAss1nfEt9ywc-
QOVOfleRxD5qtpMpc8U/mobilepresent?slide=id.gc6f9e470d_0_0)

~~~
wmccullough
So, I'm not saying they were wrong or right, but this is interesting to see
what happens when the "consensus" is a major corporation.

I'll make a prediction now. We'll be seeing things like this happening in the
Microsoft code base in a few years. I'm a huge fan of Microsoft and their open
source effort, but I'm not above believing that corporate interests win in the
end.

EDIT: Thanks for the downvotes, care to explain where I'm wrong? I'm sharing
something called an opinion.

~~~
detaro
> _when the "consensus" is a major corporation_

What do you mean by that? If consensus is required any member can veto a
proposal, not just those belonging to the major corporations?

~~~
wmccullough
I probably should clarify. I mean when consensus is a group that is making
decisions with a potential financial impact to their employer.

Google cancelled a proposal because reasons, which will be found out in
January apparently.

