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.
The problem wasn't usefulness or purity, it was that the community already standardised on a certain behaviour of promises that was not monadic. The only way to make promises monadic was to therefore not standardise "promises" at all.
At that time, monadic tasks were basically unheard of in the community. By the very nature of the monad laws, they cannot have side-effects, so they cannot be eager. This means that a user "running" an operation would observe nothing until they subscribe to it. In an imperative language like JS, this is confusing for users, and arguably not worth the breakage of the existing promise ecosystem.
Another nice property of Promises is that they can be implemented in a way that doesn't cause stack overflow for endeless recursion (and additionally, in some cases we can get "tail recursion"). This might be doable with lazy tasks too - not entirely sure. See https://github.com/paf31/purescript-safely
On the other hand, monadic tasks make many other things easier, like
* cancellation (just unsubscribe),
* synchronous error propagation, so no issues with error swallowing and perhaps no post-mortem issues in node. Nothing runs before the subscription, so at the point the side effects are running, we already know whether there is an error handler. Effectively the `done` method would've been the eqiuivalent of `subscribe`, and mandatory
* defining parallel, sequential or limited-concurrency execution order after the tasks have been created
Observables are just an API for any push system based on callbacks, to make it composable and lazy. Array.forEach uses callbacks, Promise.then uses callbacks, Node.js streams are consumed using callbacks. And observables can model those cases without losing their properties (e.g. Array.forEach is called synchronously).
> Promises have always been extremely contentious in JS for a couple reasons and Domenic has had the patients 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.
I know this is probably a typo (patients => patience) but that spelling seems to work as well.
> 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.
IMHO neither as cancelability isn't a common requirement. People that need it tend to have more complex requirements and end up tying the cancellation to other chains of logic anyway. Also, unlike say catching an error, you generally want all cancellation handlers to fire rather than just the first one (like with errors).
> 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…
It seems obvious to me that the 'right' way to do it is a cancelled promise is in the error state, with the error object just being a new type of error CancelledPromise or something.
But I guess once you start getting into it, this becomes less obvious?
> 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
I don't think this was the case. The main argument of FP side was having more composability built into the core of the language.
While it's great that they were so nice, they are unable to effectively moderate GitHub and ESDiscuss. One ticket for the private member proposal had 600+ comments, some of them crazy stream-of-consciousness posts that should have been shut down.
Dealing with this volume of input requires aggressive moderation, community participation guidelines, and canonical explanations of the decision making process.
Promises work fine in some cases, but absolutely I hate how exceptions and errors are grouped into the same bucket. Oh, you're handling 4XX responses as errors? Well, now you get exceptions there as well, good luck!
e.g. If I'm sending some data over the network, I want runtime errors to cause activity to be aborted and logged. But if I get a 429, I'll want to wait and retry.
> 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?
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.
"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."
> Google trying to strong-arm control of JS for themselves.
The person championing the proposal was from Google too; this narrative doesn't make sense. Let's not conjure up conspiracy theories if "programmers disagreeing about something" adequately explains the data.
To be completely honest, they are the maintainers of one of the largest Javascript implementations.
It really only makes sense for them to have significant input on the features that make it into the language.
Mozilla, Microsoft, and Apple could have done the same thing, as they all write the software, and that only seems fair. If there is a significant reason why something won't work in one or more of the major engines, it might need to be rethought.
I'm not sure why everyone is jumping on this like it's some kind of nefarious thing. What would google have to gain from killing this proposal?
I like to complain about big companies too, but Google isn't trying to strong arm control of JS. They hire a lot of smart programmers with strong opinions who express them and it seems like a lot of those were vocal enough to make this happen. Just how it is.
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:
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:
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.
Thank you for this response. It's very well thought out and nuanced. I'm not sure what the OP had to deal with, specifically, but I share the same opinions as yourself.
Promises themselves are generic async handling work horses. Cancelling a HTTP call makes sense. Does it makes sense to cancel an operation currently happening in, say, a loop? Likely not.
Cancellable Promises are a reality in our software stack for years in Liferay and related open source projects. JavaScript is a flexible language and this is what makes it what it is, fits into different realities and skill levels. Optionally, you can use Object.defineProperty() to define how your object behaves, if it's configurable, enumerable, writable and so on. You can Object.freeze() to freeze an object. Having a Promise that the consumer can define whether or not it can be cancelled makes sense in general. There are lots of use-cases that the consumer can safely decide if canceling a promise or chaining of promises is problematic or not, e.g. ajax calls, async life-cycle of a UI component.
There are many good arguments about cancellation tokens vs promise.cancel() on ES Discuss https://esdiscuss.org/topic/cancellation-architectural-obser.... One argument that is not accurate is that "cancellation is heterogeneous it can be misleading to think about canceling a single activity". In most systems, it's implemented expecting that async operations can be cancelled, intentionally or not (network problems for example). There is no single answer for this problem, it can be wrong and dangerous, or it can be safe and predictable, that all depends on how the consumer utilizes it.
Domenic mentioned in the proposal https://github.com/tc39/proposal-cancelable-promises/issues/... that most of Googlers on TC39 are against cancellable promises, on the other hand Google Closure Library has a very robust implementation brought from labs in 2014 by Nanaze https://github.com/google/closure-library/commit/74b27adf7. It's heavily used on large real world async applications, such as Gmail, G+, Docs. It shows clearly that there is a real space for cancelling promises without being dangerous.
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?
In my opinion, having a separate cancellation token that is "threaded through" the call stack is indisposable, because sometimes you need to do more than simply pass along the token that you've got - usually it involves linking several tokens together, e.g. with another representing the state of the component as a whole, as opposed to that particular call chain.
Now, this doesn't happen often, and most of the time you do indeed end up just passing the token along. But when you do need it, it allows for code drastically simpler than any workarounds.
My takeaway from this is that first-class cancellation tokens are the right approach, but languages need some kind of syntactic sugar to eliminate, or at least reduce, the verbiage for the most common case of propagating it around.
(All of this is based on experience working with a heavily async/await codebase written in C# for the past few years.)
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.
There are a lot of stakeholders in a large organization, and a change can sometimes elicit strong reactions from involved parties. In particular, in the JavaScript world, implementors are now also explicitly represented in standard process of new language features, since given some of the preexisting warts, there can be nontrivial interactions and many JS abstractions have holes in them. A lot of language features have been rejected for a lot of different reasons. To be fair, some really unfortunate language features have been _accepted_ that have had unforeseen interactions or implementation challenges. I could name half a dozen off the top of my head.
Disclaimer: I work on V8 but was not involved in this saga at all. I don't know which Googlers in particular Domenic is referring to in his comment. And of course I make no judgment on the particular value of cancelable promises.
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.
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.
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.
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?
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.
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).
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.
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.
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...
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:
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.
Proponents were definitely vocal about it, but I don't know how well that translates into actual popularity; at least not like async/await. Nor does it seem to be the type of thing where all sufficiently experienced experts agree. As far as I can tell, it's been a divisive topic among the intelligentsia since day one. Correct me if I'm wrong.
[+] [-] cwmma|9 years ago|reply
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
2. https://gist.github.com/benjamingr/0237932cee84712951a2
[+] [-] spion|9 years ago|reply
At that time, monadic tasks were basically unheard of in the community. By the very nature of the monad laws, they cannot have side-effects, so they cannot be eager. This means that a user "running" an operation would observe nothing until they subscribe to it. In an imperative language like JS, this is confusing for users, and arguably not worth the breakage of the existing promise ecosystem.
Another nice property of Promises is that they can be implemented in a way that doesn't cause stack overflow for endeless recursion (and additionally, in some cases we can get "tail recursion"). This might be doable with lazy tasks too - not entirely sure. See https://github.com/paf31/purescript-safely
On the other hand, monadic tasks make many other things easier, like
* cancellation (just unsubscribe),
* synchronous error propagation, so no issues with error swallowing and perhaps no post-mortem issues in node. Nothing runs before the subscription, so at the point the side effects are running, we already know whether there is an error handler. Effectively the `done` method would've been the eqiuivalent of `subscribe`, and mandatory
* defining parallel, sequential or limited-concurrency execution order after the tasks have been created
[+] [-] staltz|9 years ago|reply
Observables are just an API for any push system based on callbacks, to make it composable and lazy. Array.forEach uses callbacks, Promise.then uses callbacks, Node.js streams are consumed using callbacks. And observables can model those cases without losing their properties (e.g. Array.forEach is called synchronously).
[+] [-] koolba|9 years ago|reply
I know this is probably a typo (patients => patience) but that spelling seems to work as well.
> 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.
IMHO neither as cancelability isn't a common requirement. People that need it tend to have more complex requirements and end up tying the cancellation to other chains of logic anyway. Also, unlike say catching an error, you generally want all cancellation handlers to fire rather than just the first one (like with errors).
[+] [-] jrochkind1|9 years ago|reply
It seems obvious to me that the 'right' way to do it is a cancelled promise is in the error state, with the error object just being a new type of error CancelledPromise or something.
But I guess once you start getting into it, this becomes less obvious?
[+] [-] Nekorosu|9 years ago|reply
I don't think this was the case. The main argument of FP side was having more composability built into the core of the language.
[+] [-] indolering|9 years ago|reply
Dealing with this volume of input requires aggressive moderation, community participation guidelines, and canonical explanations of the decision making process.
[+] [-] RodericDay|9 years ago|reply
"a bit of a footgun" is such a huge understatement. Now that I know about this, I'm skeptical too.
[+] [-] TheAceOfHearts|9 years ago|reply
e.g. If I'm sending some data over the network, I want runtime errors to cause activity to be aborted and logged. But if I get a 429, I'll want to wait and retry.
[+] [-] bad_user|9 years ago|reply
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.
[+] [-] wodencafe|9 years ago|reply
Google trying to strong-arm control of JS for themselves.
[+] [-] jpalomaki|9 years ago|reply
"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."
[+] [-] fenomas|9 years ago|reply
The person championing the proposal was from Google too; this narrative doesn't make sense. Let's not conjure up conspiracy theories if "programmers disagreeing about something" adequately explains the data.
[+] [-] Klathmon|9 years ago|reply
It really only makes sense for them to have significant input on the features that make it into the language.
Mozilla, Microsoft, and Apple could have done the same thing, as they all write the software, and that only seems fair. If there is a significant reason why something won't work in one or more of the major engines, it might need to be rethought.
I'm not sure why everyone is jumping on this like it's some kind of nefarious thing. What would google have to gain from killing this proposal?
[+] [-] 0xCMP|9 years ago|reply
[+] [-] cwmma|9 years ago|reply
[+] [-] Gaelan|9 years ago|reply
[+] [-] 65827|9 years ago|reply
[+] [-] dreeko_|9 years ago|reply
[+] [-] paradite|9 years ago|reply
Context: https://github.com/tc39/proposal-observable/pull/97
[+] [-] wycats|9 years ago|reply
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:
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:
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:
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...
[2]: https://github.com/tc39/proposal-cancelable-promises/issues/...
[3]: https://msdn.microsoft.com/en-us/library/dd997289(v=vs.110)....
[4]: http://ember-concurrency.com/#/docs
[+] [-] BinaryIdiot|9 years ago|reply
Promises themselves are generic async handling work horses. Cancelling a HTTP call makes sense. Does it makes sense to cancel an operation currently happening in, say, a loop? Likely not.
[+] [-] eduardolundgren|9 years ago|reply
There are many good arguments about cancellation tokens vs promise.cancel() on ES Discuss https://esdiscuss.org/topic/cancellation-architectural-obser.... One argument that is not accurate is that "cancellation is heterogeneous it can be misleading to think about canceling a single activity". In most systems, it's implemented expecting that async operations can be cancelled, intentionally or not (network problems for example). There is no single answer for this problem, it can be wrong and dangerous, or it can be safe and predictable, that all depends on how the consumer utilizes it.
Domenic mentioned in the proposal https://github.com/tc39/proposal-cancelable-promises/issues/... that most of Googlers on TC39 are against cancellable promises, on the other hand Google Closure Library has a very robust implementation brought from labs in 2014 by Nanaze https://github.com/google/closure-library/commit/74b27adf7. It's heavily used on large real world async applications, such as Gmail, G+, Docs. It shows clearly that there is a real space for cancelling promises without being dangerous.
[+] [-] amitzur|9 years ago|reply
[+] [-] int_19h|9 years ago|reply
Now, this doesn't happen often, and most of the time you do indeed end up just passing the token along. But when you do need it, it allows for code drastically simpler than any workarounds.
My takeaway from this is that first-class cancellation tokens are the right approach, but languages need some kind of syntactic sugar to eliminate, or at least reduce, the verbiage for the most common case of propagating it around.
(All of this is based on experience working with a heavily async/await codebase written in C# for the past few years.)
[+] [-] antirez|9 years ago|reply
[+] [-] titzer|9 years ago|reply
Disclaimer: I work on V8 but was not involved in this saga at all. I don't know which Googlers in particular Domenic is referring to in his comment. And of course I make no judgment on the particular value of cancelable promises.
[+] [-] zcdziura|9 years ago|reply
[+] [-] dchest|9 years ago|reply
[+] [-] batmansmk|9 years ago|reply
[+] [-] BinaryIdiot|9 years ago|reply
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|9 years ago|reply
[+] [-] taneq|9 years ago|reply
[+] [-] stavros|9 years ago|reply
[+] [-] k__|9 years ago|reply
(probably because it was Bad, but it's sad to See that one company has so mich power)
[+] [-] codedokode|9 years ago|reply
[+] [-] nraynaud|9 years ago|reply
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|9 years ago|reply
[+] [-] trungaczne|9 years ago|reply
https://github.com/tc39/proposal-observable/pull/97
[+] [-] Raed667|9 years ago|reply
Did I miss an explanation somewhere?
[+] [-] kbody|9 years ago|reply
[+] [-] Klathmon|9 years ago|reply
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.
[+] [-] novaleaf|9 years ago|reply
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|9 years ago|reply
[+] [-] BigJono|9 years ago|reply
[+] [-] strictfp|9 years ago|reply
[+] [-] semi-extrinsic|9 years ago|reply
Here are some slides with technical details of the proposal:
https://docs.google.com/presentation/d/1V4vmC54gJkwAss1nfEt9...
[+] [-] zoeysaurusrex|9 years ago|reply
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.
[+] [-] mfukar|9 years ago|reply
[+] [-] _greim_|9 years ago|reply
Proponents were definitely vocal about it, but I don't know how well that translates into actual popularity; at least not like async/await. Nor does it seem to be the type of thing where all sufficiently experienced experts agree. As far as I can tell, it's been a divisive topic among the intelligentsia since day one. Correct me if I'm wrong.