Closed domenic closed 8 years ago
Two questions:
Promise.race()
again or do you really only want to cancel the async function after the read
or delay
have settled?.cancelIfRequested()
return? A cancelled promise? Or does it not return but complete with an abrupt cancellation? (Do these things cross function boundaries now?)Did you forget the Promise.race() again or do you really only want to cancel the async function after the read or delay have settled?
Promise.race seems irrelevant.
You can only cancel after the read or delay is settled anyway. Before that there's no way for code outside the function run since the async function itself is running.
What does .cancelIfRequested() return? A cancelled promise? Or does it not return but complete with an abrupt cancellation? (Do these things cross function boundaries now?)
Please read the spec... more like the latter.
You can only cancel after the read or delay is settled anyway. Before that there's no way for code outside the function run since the async function itself is running.
I'm not sure what you mean. Let me explain with an example:
async function demo(cancelToken) {
await.cancelToken = cancelToken;
try {
await delay(2000);
} finally {
console.log("done");
}
}
console.log("start")
demo(new CancelToken(cancel => {
delay(1000).then(() => {
console.log("cancelling");
cancel();
});
}))
Will "done" be logged after one or two seconds? Clearly there is code running concurrently to the async function and the two-second delay.
I am assuming on purpose here that cancelToken
is not passed to the delay(2000)
call and that promise doesn't get cancelled.
Two seconds. Because here is what the VM does:
NOW execution of the function is suspended for 2 seconds, and we can execute other code (such as console.log("cancelling"); cancel();
. We cannot do so before this point, so it's pointless to insert an implicit cancelToken.cancelIfRequested()
before the await delay(2000);
.
And yes, cancel()
will be executed at the one second mark. But the async function is suspended for 2 seconds, so there's no way for it to know about that. If you want to cancel during the suspension, you have to turn over the cancel token to the operation that causes the suspension, by doing await delay(2000, cancelToken)
.
(This is why it is so hard to find a good use case for cancelIfRequested()
or await.cancelToken
, by the way: it is almost always better to just pass the cancel token along to the child operation.)
But the async function is suspended for 2 seconds, so there's no way for it to know about that.
Sure there is - that's what we registered the cancelToken
for through await.cancelToken
. We could desugar await x
to
cancelToken.cancelIfRequested()
await Promise.race(x, cancelToken.promise.then(c => { cancel throw c; }));
which would cause "done" to be logged after only 1s in the above example.
Isn't that what we want, to break and run our finally
clauses as early as possible after the token is cancelled?
That's an interesting proposal as well, thank you. Although Promise.race only takes one argument, and ignores canceled promises anyway, I think I understand what you're getting at.
Ah, really should've studied https://domenic.github.io/cancelable-promise/#sec-promise.race better, I was under the mistaken assumption that cancellation worked like rejection there.
Just a quick update, I've changed await.cancelOn
(or whatever we name it) to a nullary/unary operator in my draft. I believe that's more attractive and signifies the special behaviour (influencing await
s around the whole function) better than a "property" with an assignment-like behaviour. And it's probably also easier to specify.
The best we could get from a clarity perspective might be a scope-like behaviour:
await.cancellation from cancelToken in {
await …;
}
(but we would need to consider what to do with nesting, maybe even allow token composition through that)
The reason it's going to be specified as await.cancelToken = is because it fits with the existing meta-property concept used elsewhere. There is no dotted unary operator concept so we won't be introducing one.
You sound like it's decided already?
The only meta properties I know about are new.target
and function.first
(in generators, I might misremember the name), and those act purely as "getters" - or plain keywords/nullary operators, rather. It seems that assignment to them introduces a new concept anyway, doesn't it?
Yeah, it was decided at the last TC39 meeting.
Ah, I had interpreted the meeting notes as only pursuing this path further, not to settle on the exact syntax. Anyway, I should be glad my idea was adopted :-)
Wow, thanks! I guess I should open seperate issues for any problems I see with that draft or whishes I have to extend it?
Yeah, that would be best.
I am extracting this from #19, which in general is about an entirely different proposal unrelated to this repo (and thus closed), but inspired an idea which could be useful for this repo.
The idea is to have syntax sugar for automatically inserting
cancelToken.cancelIfRequested()
after everyawait
point inside an async function. Doing this implicitly has gotten pushback in the past, but doing it explicitly seems to make sense to me. The strawman is then that we would allowas sugar for
(using a slightly-tweaked version of the motivating example for cancelIfRequested, see #16).
This may be similar to what @inikulin was asking for in #18.
/cc @wycats since I believe this might address some of his concerns.