-
Notifications
You must be signed in to change notification settings - Fork 106
How to cancel a running async function #27
Comments
in a similar vein we have a lot of code that relies on errors coming from outside sources, but I can't find a way to abort a running async function: async function () {
// ... get a child process ...
// caused by some things that do not actually kill the process
// e.g. stdio causing SIGPIPE
child_process.on('error', abort);
// ... do things until .on('exit') ...
} for a lot of things like streams, where errors are disjoint (not in the same stack as a pending callback) it would be nice to have a clear way to abort. |
Throw an error maybe? Instead of aborting. |
I'd consider this to be a blocking issue in the current design. If the OP case wasn't addressed (aborting from outside), I would never use async functions and would just stick with the slightly uglier generator+promise option with a runner util. A possible idea to address: async function foo() { .. }
var ret = foo();
ret.value; // promise
ret.return(..); // analogous to generator `return(..)`
// returns either its own separate promise
// or the original `ret.value` promise so... foo().value.then(..)
// instead of
foo().then(..) Not wonderful, but certainly workable compared to no solution. |
@getify How does function* () {
// Set up state and resources
try {
yield thing1();
yield thing2(); // Say you cancel while waiting for this...
yield thing3();
} finally {
// Cleanup
}
} If you just cancel that generator with |
The finally block will be executed by 2015-02-19 23:48 GMT+01:00 Petka Antonov [email protected]:
|
@mnieper nothing about that in the es6 spec, and v8 doesn't implement it so cannot test |
See here http://people.mozilla.org/~jorendorff/es6-draft.html#sec-generator.prototype.return which relies on http://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresumeabrupt which in turn resumes a suspended generator in step 11. Now look at http://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresumeabrupt where the semantics of yield are given. -- Marc (Traceur implements all this.) 2015-02-20 8:32 GMT+01:00 Petka Antonov [email protected]:
|
As a side note, TBH, I've read those several times before, and I'm still not clear exactly where in there:
I don't know if BabelJS is fully up to spec on this |
@getify Babel uses regenerator for generators and async functions. Also important to note that this is also the behaviour of Traceur. |
@mnieper i know that's what it does, I was saying I haven't yet been able to fully explain that by the wording I see in the spec. I've read through it carefully several times, and I still keep getting lost. |
btw, I just threw together this quick ugly diagram illustrating my point about how a cancelable async function would fit: The main takeaway is that this doesn't rely on artificially injecting cancelation through shared scope (i.e., using a side-effect to tell step 2 to abort), nor does it rely on promise cancelation (action at a distance). It instead treats observation and cancelation as separate capabilities (much like early promise/deferreds did, btw!), which only the initiating code ( |
other use case that is fairly important that we are using from generator-runner code: async function () {
try {
// init cleanup directives
let lock = await lockfile(file);
lock.oncompromised = cancel;
// stuff that should stop if lock becomes compromised
}
finally {
// cleanup
}
} |
I think this question is really just about what the de facto or de sure standard will be for Promise cancellation. I personally think a token-based cancellation model that is passed in separately from the Promise instances is a good option for this, that also works great with async/await (see C# precedent for what this looks like at scale). But I'm not sure there is anything that can/should be changed in the async/await proposal separate from what needs to be done in furthering the models for cancellation around Promise-based APIs. |
@lukehoban my concern is when you need to pass the ability to cancel to APIs in use by the async function like the lockfile example I put up. |
Having to pass around a cancelation token definitely harms the composability of many async functions, unless some de facto standard emerges where the first (or last) argument is always this token, kinda like the ugly "error-first" style of callbacks in node. Cancelation of promises is way, way far away from a given. A lot of high-visibility folks are already assuming it'll happen, but there are others (like me) who are ready to die on a sword to try to stop that from happening. This is not as simple as just "wait for promises to figure it out". Promises shouldn't be cancelable. If that means that async functions shouldn't / won't be cancelable, fine. Just say that. It means some of us won't use them, at least not in all cases, but that's a better outcome than just following the cancelation crowd. Consider this for a moment: let's say that promises do become cancelable. What does that mean for a running (paused) async function instance if such a signal were somehow received? Is it going to end up doing something almost like what generators do, where it aborts the running context, but then still lets any waiting This spec could take the lead and come up with something that makes more sense than cancelable promises, and then maybe the |
I would just like to reinforce, it is not always the outside (where a controller would be) that wants to be able to stop execution, sometimes conditions that compromise a task need to be hooked up inside of the function itself. For this use case I am not sure promise cancellation is even a _viable_ solution, though things like generator's |
@bmeck perhaps I missed it, but if the cancelation signal needs to come from inside the async function, why can't |
@getify timing is the issue, right now you can throw which will work if you do not have out of band cancellation. In my example, we need to allow the lock to fire a callback to cancel our function. This is because the lockfile library is the authority on _when_ the lock is compromised. If we want to emulate this with try/catch/finally we would need any await to be followed with a check for any cancellations. This would be more akin to |
Nods. I guess I was assuming all such out-of-band-cancelation could be given access to the controller. which, as you said, already maps to essentially what you're doing now. I can't envision another way of that working. |
Just wanted to add another piece of information in support of my suggested "controller" object pattern: Revocable Proxy.
Object destructuring makes this still super easy to deal with: var {promise:p1} = asyncFn();
var {promise:p2,cancel} = asyncFn();
// later... `cancel()` |
I'm closing this for now. Once the library space figures out how cancellation should work we can start talking about syntax. |
Imo promises and async/await is an abstraction to make async code look sync. 99% of times it will be enough - like replacing node errbacks. If the abstraction doesn't fit the use case, than something else than promises (and therefor async/await) should be used, like observables. |
I'm totally in agreement that if you want it to be cancellable, that promises aren't the correct primitive to use, and therefore async functions aren't the correct primitive to use. |
async functions don't have to return promises. that's a choice (and a bad one IMO). this is tail-wagging-the-dog behavior to work backwards from "i want to cancel" to "promises shouldn't be cancelable" to "async functions are the wrong primitive for cancelable actions". |
That's just not true. I consider promises an observation of a future-value completion. That's a read-only operation. I don't think promises should have any external notion of change or cancelation. But that's entirely orthogonal to the idea that the set of tasks that an |
@getify What is the standard cancellation mechanism people exposed? It doesn't exist, there are many conventions. And those uses are far outweighed by people who don't care about cancellation and use promises as they are. Async functions as they are now are for this vast majority. I understand you consider cancellation a critical scenario but I simply disagree with you that cancellation is so important that it needs to be addressed before shipping async functions. Many people clearly want cancellable promises (it has been added to many libraries and discussed far and wide). Future cost has certainly been discussed (search the meeting notes). I also agree with your concern (as I've stated), I just disagree with your proposed solution (again, as I've stated). I don't know where you read that the goal of async functions is to obviate generator+promise pattern (as I've stated, the goal is to replace promise-returning functions). |
Async functions should have the same semantics of promises since they're trying to be the same as other functions that return promises, just with nice syntax for doing more async actions internally if needed. |
That makes absolutely no sense to me. Would you say that the design motivation behind generators was to replace iterator-returning functions? That completely discounts all the power/magic of locally-pausable stacks that both of them afford. The predominant view of the generators + promises pattern is that its primary goal has nothing to do with the return value being an iterator -- that's an implementation detail -- but rather that The only problem is that this pattern requires a lib to hook up the yielded promise to the iterator.next() call. Then enters I'm utterly lost on that assertion. |
cancelation of async operations has been long precedented before any of the current mechanisms we're discussing arrived on the scene. The most common example would be cancelable Ajax, which has been around a long time (even longer in libs). |
Again, if we look at the comparisons the community is predominantly using the pattern without cancellation semantics. |
@RangerMauve --
|
|
@RangerMauve this brings up a good question though, how many people actually want cancellation, and how many can cancel operations. A lot of existing code does not even have a means for cancellation since all the existing libraries using callbacks etc. could easily place cancellation inside of the control flow code. I would like to avoid using the "everyone is doing it, so it must be better" approach when things such as Compositional Functions are a superset of I am on the list of people who actually use cancellation, I have my own generator task library. That being said, most people don't even understand the use case for cancellation since most DOM/npm APIs don't have cancellation abstractions. That said, all of the Promise cancellation abstractions are interesting, but have yet to find one that handles my use cases or those involving resource locks. |
Yes async functions make async code look sync. It does not accomplish this with a generator, the generator is just your chosen implementation detail (and conceptual model) of the async function body. I've already addressed most of your above points and I'm not interested in continuing to restate them. |
If you're looking for language precedent (which I'd argue is stronger than user-land precedent), look at the iterator returned by generators. It's a compound object that returns methods for observing (and controlling) the output of the operation (aka I drew my inspiration for the controller object returned from an |
My proposed semantics meaning how people use my lib? I didn't argue that. As we're all clearly aware, relatively few people use my lib. My proposed semantics meaning the design of a controller object coming back from an async function? Zero, since the language hasn't done it. However, there's a whole lot of people using the iterator controller object coming back from generators, and that's the direct inspiration and (language) precedent for that idea. |
In terms of node, the only things that are particularly cancellable are things with streaming APIs In the browser it's pretty much the same with XHR and Websockets that are cancellable. Really, this leads me to re-enstate that Observables or the such are a better primitive for working with cancellable actions. |
@RangerMauve I agree, and just want an option to use alternatives with similar syntax to |
New language features should exist to better development for the users, not to satisfy what someone considers to be "a stronger language precedent". The usage of language features by the community should definitely be considered more heavily in development of new features, otherwise who are those features being made for? Just to add bloat? |
@RangerMauve using language precedent for future design is one way of achieving language design consistency, which leads to better learnability, etc. Paving the temporary hack cowpaths that userland comes up with while waiting around impatiently on the language actively does not aid in that particular goal. |
is there anyone seriously saying right now, "that iterator-returned-from-generator design decision was bad, because for example it made chaining multiple sequential generators together harder/more awkward"? If not, then I think the design of iterator-controller-object-as-return-value is just as valid a suggestion/inspiration for language design as anything else userland comes up with. |
Just because a function isn't labelled But one of the most common usecases is just returning a single value async, throwing async, catching that async exception. Just like we do in synchronous code. And for that simple usecase, |
@getify I just stumbled ac cross this discussion because I need to make things cancellable in a project that makes heavy use of async/await. If they had gone with your suggestions, my life would have been tremendously easier now. |
async programming in js is not just one possible path (as it is in other languages) it's the only path, so its async primitives should be designed to be more than demoware. That async functions can create huge chains of resumable code blocks that can't be externally cancelled by simply not resuming, which is low hanging fruit, is just ridiculous. Has tc39 ever done a retrospective on this? Any acknowledgement that this was a mistake? |
This comment has been minimized.
This comment has been minimized.
For anyone who's stumbled on this looking for a solution, I found this to be a great article and work-around: |
@vincerubinetti FWIW, I'm not a fan of the approach in that article, as it doesn't truly proactively cancel the functions immediately, only silently when they later resume. I wrote and now use CAF for managing async functions with true-cancellation semantics. |
@getify That looks like a good library, I will definitely look into using it for my project. I'm not exactly sure what you mean by the statement "it doesn't truly cancel". Do you mean it doesn't cancel in-flight requests like fetches? If so, the article does kinda mention that at the bottom as an after-thought (not super helpful, I know). In my stackoverflow post I give an example of how to integrate AbortController into the method as well. Do you mean that your library can also "in-flight" cancel things besides fetches, like computationally intensive arbitrary asynchronous functions? It seems to me that there's really nothing wrong with the article's approach, and would still be useful to a lot of people, as long as you understand what it's doing (which can be said of any solution really). If I understand how it works correctly, it just makes you split up your function into break points (in between But this is just from a very cursory look at your library docs and src, I might be missing something. I'll look into it more later. *EDIT, also I should clarify that I didn't LIKE the solution, but I did find it to be the best available option (before I knew about your CAF library). The best solution would've been something built into javascript itself, of course. |
I don't want to get too OT for this thread, but briefly...
I mean that the article's concept of comparing nonces doesn't do the comparison until after the await/yield has resolved, meaning that the ajax request (or whatever) has fully completed. If it stays pending, potentially forever, this async function/generator is just left in that pending state and not cleaned up. Generators are nice for this use-case precisely because they can be stopped immediately using the return/throw methods. IOW, even if you didn't signal the cancellation down into the fetch() call, I would consider the async function being "truly cancelled" if the generator was aborted right away and not continuing to wait for that operation to finish or not.
CAF uses AbortController cancellation tokens, so they can indeed be passed along to any other utilities that know what to do with AC instances, such as I'm hopeful that the JS community follows the lead of fetch() (and CAF) and standardizes widely on AC for cancellation. |
generic promise cancellation is not possible and doesnt make sense. |
9 down votes say it does |
Is it possible to short-circuit a running async function like one can do with a generator by calling its return method?
A use case would be to have a finally block invoked in order to clean up resources, etc.
The text was updated successfully, but these errors were encountered: