Closed SEAPUNK closed 7 years ago
This proposal experienced significant opposition from within Google and so I am unable to continue working on it.
That sucks. In theory, somebody else could pick this proposal back up and champion it though, right?
They could, but they would be blocked by other Googlers in TC39, so it would be fruitless.
Could you tell us why they are opposed to this, please?
I'm sorry, I cannot really participate in these discussions any more, for my own mental health; it has been draining enough pursuing the fight internally, and losing. (In addition to the plethora of issues opened here by various people who believe they have a superior proposal, which was a constant drain.) They'll have to speak for themselves.
I'll be unsubscribing from this thread and I ask that nobody @-mention me.
I see. Thank you, and I hope you get to feeling better!
😞
I've been following this repo for awhile and seen all the noise of others. It's a lot of pressure trying to get this one right.
I leave you with an internet hug 🤗
I'm sorry, I cannot really participate in these discussions any more, for my own mental health
I'm not @-mentioning you here, but I'm glad you're taking time to yourself and I know the efforts you invested on this in the meetings.
I hope you feel better soon.
🤗 feel well, people do appreciate the work you do.
Found this link explanation
Can someone please show me the link to the counter arguments please ?
Counter arguments would be nice if anyone is aware of them. Sounds like some Google folk objected for some reason:
They could, but they would be blocked by other Googlers in TC39, so it would be fruitless.
I wonder what the Googlers objections were.
This proposal getting withdrawn seems a bit mysterious at the moment, and I know there are APIs (like fetch) that were hoping to get a solution soon.
From which stage this propose was dropped?
stage 1?
@mqklin and @dinoboff yes it was stage 1
@landonpoch thanks. Do you know any proposes that was dropped at stage 2 or upper? Sorry for the offtop
Object.observe was stage 2.
Generally speaking though it seems like at stage 2:
"The committee expects the feature to be developed and eventually included in the standard"
Expectations are not certainties.
This is sad news. I really believe that there needs to be support for cancelling a promise in the specification. The fact that employees of Google on the TC39 committee are able to block proposals like this kind of worries me. I thought the whole point of TC39 was to make unbiased decisions on what goes into Javascript.
As far as I could find, I couldn't find any real valid reasons as to why this proposal should not go through. I saw some of the other contributions and they were flawed, this was the only thought out proposal. Incredibly sad.
@Vheissu i'm sure by the next meeting in January, the reasons will be made apparent.
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.
@ljharb as much as I agree with what you're saying, it would be nice to have a little transparency so that we could at least know of the arguments against this proposal.
@adrianmcli it's worth waiting at least until the January TC39 meeting to learn what those are.
Beyond all else, what raises alarm in me is the comment Domenic made about his mental health. Does anyone have any insight on what he means? I can empathize with how draining making a logical case can be when others truly believe in their alternatives the way you believe in yours. Is this that or is this something more?
Maybe this is just me not having spent time at a larger corporation, or as part of a technical committee of a major language, but anyone coming to feel like that as an outcome of trying to do their best seems ugly and unacceptable.
It's possible that this was blocked in favour of Observables: https://github.com/tc39/proposal-observable
(I don't yet have an opinion on the merits such a decision if it was made for this reason. Observables bring quite a lot to the table, but they do also bring added complexity.)
@ablakey Someones health is a persons own business, so it is best not ask — either them or third parties — to publicly share information about a private affair. That also includes speculating, you don't want people to feel the need to set the record straight about stuff that was no one elses business in the first place.
@Robbert you're right. I should rephrase to not target a specific person or private matter, lest I accidentally push them to talk about it publicly. But if it's to be more general, I may as well raise the discussion in a different forum.
Usually in other working groups there is a chance to explain why a proposal was withdrawn, for example the Scheme working group provided the ballot and the results of their votes along with any extra explanation as to why they voted the way they did: http://trac.sacrideo.us/wg/wiki/WG1Ballot1Results
It would be nice if that happens in this case; achieving consensus is important but transparency is equally important especially when it affects many many more users and developers.
experienced significant opposition from within Google
must be against their dragnet gag orders /s
@ablakey maybe will be interesting to read: https://medium.com/@thejameskyle/dear-javascript-7e14ffcae36c#.k0isj0qfz
Found this link explanation
Can someone please show me the link to the counter arguments please ?
I don't have links, but have a few counter arguments of my own:
OTOH It seems the main argument in favor of the cancel state(according to the presentation) is that the following snippet is not very elegant:
try {
await fetch(...);
} catch (e) {
if (!(e instanceof CancelError)) {
showUserMessage("The site is down.");
}
} finally {
stopLoadingSpinner();
}
But rejection is not the only way to represent cancellation, which can also be a return value:
try {
let result = await fetch(...);
} catch (e) {
showUserMessage("The site is down.");
} finally {
stopLoadingSpinner();
}
In the above, we could consider the fetch to be cancelled by inspecting the return value. Also, you probably don't want cancellation to be propagated like an exception.
@tarruda any cancellation have sense - at least to prevent following code execution, which can be much more resource-consuming than cancelled action itself.
A third state is an unnecessary when cancellation can already be represented as a special case of accept/reject.
There are lots of problems raised with this alternative as well though (for instance, if "special case" means "special value" then we've moved even further away from Promises as generic type containers for any value).
This is just an extremely tricky bit of functionality with many, many cross-cutting concerns and subtle gotchas. Other languages bridge it with a very different model from Promises themselves. But too many apis are Promise-based for things to move forward without some solution for cancelation. Extremely grateful for all the work put into trying to find a solution here.
I would agree with @tarruda, I do not see why cancellation as a custom exception can not cover most of the cases?
Implementing a third state does not seem like a good solution.
I think the way a ReadableStream can be cancelled with fetch is good enough, simply reader.cancel()
Cancellation is a valuable primitive state as it allows preventing downstream work when other factors intervene. No longer need to render a report? Cancel the long and expensive request to the API. Saves resources on the client. The server can do the same thing. Client closed the connection before our long running database query finished? Stop the query to release the resources for other requests.
This proposal was well thought out and clean. I'm really looking forward to hearing the counter arguments.
Just a quick thought regarding @tarruda's suggestion:
OTOH It seems the main argument in favor of the cancel state(according to the presentation) is that the following snippet is not very elegant:
try { await fetch(...); } catch (e) { if (!(e instanceof CancelError)) { showUserMessage("The site is down."); } } finally { stopLoadingSpinner(); }
I agree that his doesn't feel particularly elegant. But the bigger problem that sticks out to me is interoperability.
This approach may work well inside a single author's code, but there's no guarantee that multiple authors will share an implementation of a CancelPromise
class (or whatever you call it). Symbols seem like a better fit, but even if the community standardize on something like const CancelPromise = Symbol.for('CancelPromise')
there would still be some outliers that weren't familiar with the convention.
@tarruda Actually, adding "canceled" would be a fourth state: { in-flight
, succeeded
, rejected
, canceled
}.
OK, so you could combine rejected
and canceled
. Fine, but you can go further. Why not combine succeeded
and rejected
, too? Essentially, a promise is in one of two states: { in-flight
, completed
}. When it transitions from in-flight
to completed
, user code needs to be invoked. Whether the promise succeeded or failed could be carried in the completion payload. Success and error completions could be handled via convention, similar to how Node callbacks typically accept errors in their first parameter.
The argument for keeping succeeded
separate from rejected
is that it's extremely convenient. But it's certainly not essential. Regarding cancellation, the question is really whether the convenience of making it first-class outweighs the conceptual complexity.
The process for the TC39 committee is here: https://tc39.github.io/process-document/
The champion of this proposal withdrew it and maybe there is some other champion within the committee who will pick it up. If there are objections to this proposal it would be much more transparent to have had a vote on it rather than have its champion withdraw it. It's a shame that the committee member who was the champion was stressed out and it looks like burnt out by the discussion around this proposal.
I would recommend that everyone who has an opinion on this to post a blog somewhere about it and continue the discussion there or on Freenode IRC (something like channel #tc39-promises
?). If someone has a really strong opinion they should continue the work; polyfills are something we can live with in the JavaScript world, what with babel and other transpilers around. When the issue is closed the conversation here will be closed but definitely should continue elsewhere.
What I like about TC39 is that they are making an effort to have polyfills to make it possible to use these new language features today and this proposal wasn't any different. So instead of idle talk, we can download the code, try it ourselves and then write a blog about it with all the technical details and reasoning as to why we think this should be picked up by another champion or why you think it's ok to drop this proposal.
@sstelfox you made some good points as to why cancelling some async operations can be useful, but can you give an argument as to why it needs to be part of the promise API?
I'm really looking forward to hearing the counter arguments.
fs.stat
)?Promise.cancel
after a bunch of code already depends on it, at which point it can no longer be removed.await timeout(5000);
? It would be possible with a intermediary object used to manage the timer:let timer = new Timer();
emitter.on('some-event', () => timer.cancel());
await timer.timeout(5000);
But having a standardized Promise.cancel()
brings nothing to the table here, as only the implementation of Timer
is calling it directly.
@tarruda Actually, adding "canceled" would be a fourth state: { in-flight, succeeded, rejected, canceled }.
OK, so you could combine rejected and canceled. Fine, but you can go further. Why not combine succeeded and rejected, too? Essentially, a promise is in one of two states: { in-flight, completed }. When it transitions from in-flight to completed, user code needs to be invoked. Whether the promise succeeded or failed could be carried in the completion payload. Success and error completions could be handled via convention, similar to how Node callbacks typically accept errors in their first parameter.
The argument for keeping succeeded separate from rejected is that it's extremely convenient. But it's certainly not essential.
Agreed.
Regarding cancellation, the question is really whether the convenience of making it first-class outweighs the conceptual complexity.
IMHO it doesn't, mainly because it doesn't fit into synchronous programming style using async
/await
, which is already seeing widespread use.
But having a standardized Promise.cancel() brings nothing to the table here
@tarruda This proposal did not include a "standardized Promise.cancel()" method. It was based on cancelation tokens. It was also very compatible with async / await syntax IMO.
@tarruda I agree that languages have to be careful but that's what these discussions are for. I also agree not all operations can or should be cancellable, but this mechanism doesn't require all promises to support cancellation.
The proposal here is to provide a standardized mechanism of cancelling in flight async requests, and allow for specific callbacks to (optionally) be triggered under the circumstances. In a lot of cases neither success
or rejected
are correct ways to reason about something that no longer needs to happen.
Quite a few developers will take a success
message to trigger additional processing, storage, event notifications and will not take into account the case where the information is no longer needed. Perhaps this is an oversight on their part, but I don't personally consider it reasonable to assume cancelled
as a successful state. Similarly with a rejected
state, this is most commonly associated with an error state which cancellation isn't. It was a designed and intended action and shouldn't go through error handling logic while still allowing a path through a cleanup and optional callback mechanism.
The cancellation token mechanism allows for a consistent way for users of the language to chain these cancellations together as well downstream through libraries and chains of events, controlled by the original caller. You can create a CancelToken
passing it through a tree of calls and upon triggering of that cancellation handle, it will propagate to the leafs of the call tree and cease performing any needless work that supports the mechanism.
By having that a standard part of the language, library and framework authors can implement it as an optional but consistent feature to save resources.
I'm sad that this was withdrawn. I was just discovering this proposal and getting excited about it. I hope that my feedback didn't contribute to the stress.
When I added cancellation to the polymer static analyzer I found that I didn't really need much. cancelToken.throwIfRequested()
and a clear way of distinguishing a Cancel from a normal exception was sufficient.
I put together a polyfill of the parts of this proposal that don't require new syntax here:
Feedback very welcome.
So, the "cancellation tokens" proposal is being withdrawn here. What about promise cancellation in general? We rely heavily on cancellation in our client apps, and we use the https://github.com/petkaantonov/bluebird library for promises that support cancellation. We won't move to built-in JS promises until cancellation support is there.
@tarruda I think it makes a lot of sense for cancellation to be a separate API, for developer convenience.
Example cancellation scenarios for us:
We were originally on Bluebird2 which bundled cancellation with rejection, which gave us cancellation but it was a huge pain to use. We encountered so many subtle bugs that we ended up writing some utility code to work around them. Also, developers had to consciously think about cancellation whenever writing a rejection handler or calling cancel(), since cancellation would bubble up as an unhandled promise rejection if we didn't explicitly handle it. And it was a pain to unit test all these cases.
Bluebird3 made things a lot easier for us by making cancellation a separate top-level concept with different semantics than rejection - see changes here: http://bluebirdjs.com/docs/api/cancellation.html
Now, our rejection handlers just have to worry about rejection, and when we call cancel() we don't have to worry about it being unintentionally raised as rejection, or subtle timing issues caused by cancellation being async. The separate syntax is nice, but the main reason I see for having cancellation be a separate concept is the semantic differences.
tfw google has a monopoly on JS.
If cancellation was baked into promises from the start then promise.cancel()
would be pretty nice, but if we added it in today it would break existing code. e.g. this is a fairly common pattern:
class PromiseCache {
constructor() {
this._cache = new Map();
}
get(key) {
if (!this._cache.has(key)) {
this._cache.set(key, this._compute(key));
}
return this._cache.get(key);
}
async _compute(key) {
// some work that computes a value
}
}
That code is correct today, and if we added promise.cancel()
and cancellation propagated to a promise in the cache, then it in effect cancels all future requests for the corresponding key.
Breaking the invariants of existing code is a non-starter here, which pretty much leaves us with cancellation causing either a resolve or a reject with a special Cancel object. Thus, CancelTokens and Cancel objects that are kinda like exceptions.
I missed the idea on the first read, sorry for the confusion regarding async/await. Now I can certainly see how powerful the concept of cancellation tokens can be for chaining async operations that can be cancelled.
Still, making changes to the language or Promise API(adding "cancelled" state) seems unnecessary, more so when you consider that most of the benefits of cancellation tokens come from following an API convention and not from changes in the existing tools. Maybe I'm missing something, but can someone highlight what the proposal would bring to the following example?
async function job1(token) {
let result = await job2(token);
if (token.cancelled) return;
return result;
}
async function job2(token) {
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += await someAsyncComputation();
if (token.cancelled) return;
}
return result;
}
async function job(maxTimeout) {
let token = new CancelToken(async (cancel) => {
await timeout(maxTimeout);
cancel();
});
let result = job1(token);
if (token.cancelled) {
throw new Error("timed out");
}
return result;
}
This is indeed a powerful pattern, but it can done without any changes to the language or ties with the promise API.
@tarruda Based on the above sample, if someAsyncComputation
was an xhr request it can't be aborted. It is just that the caller doesn't get the response.
As far as i know you can't cancel a promise, you either success or fail miserably, but you can not step back and say, hey no I was joking it wasn't really a promise!
P.S. just joking
@mjerez-radical My thought on this is success means the promise has fulfilled its obligation, failure means it was unable to, and cancellation means the original requestor of a promise no longer needs it, so it's pointless for the promise to continue to try and fulfill it. It's not really "breaking a promise", as much as it is a state that indicates that the result/sequence of events is no longer needed.
My apologizes in advance if I've missed something, but it seems this proposal has been abruptly withdrawn without much explanation. Is there a reason why?