You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add a utility for knowing an AbortSignal was aborted #37220
It would be very useful to be able to write this code in a more ergonomic way, talking to @getify about this in the CAF repo a utility was suggested:
const{ once }=require('events');// returns a promise for when the signal was abortedasyncfunctionaborted(signal,resource=null){if(signal.aborted)return;// early return on aborted signal// the kWeak bit not implemented yet is so that the event listener doesn't leakawaitonce(signal,"abort",{[kWeak]: resource});}
Which would let you do
awaitaborted(signal);// Or// or some other resource, when the request gets GCd the listener gets removed automaticallyawaitaborted(signal,request);
Any opinions on this? (Personally I am in favour) If we add such an API under what module would it live?
FWIW, in CAF, the promise I create for an abort signal actually rejects when the token is aborted... this makes detection of the abort a little easier (like with try..catch around the await). I think it would be more useful if aborted(..) made a promise that rejects rather than resolves. If not, I just have to create some more machinery around it to not create breaking changes in CAF.
In any case, I would envision shipping some sort of polyfill for this util along with CAF, because CAF is designed to work in both Node and the browser. So interop with the browser env is important here. Maybe it's a good idea to float such a util with WHATWG (or whoever) to see what their interest level is?
Other than being significantly shorter the selling point for me here is that the code is significantly safer as well - since you don't have to remember to remove the listener to the signal.
Maybe it's a good idea to float such a util with WHATWG (or whoever) to see what their interest level is?
I'm also wondering if something like what was done with util.promisify(..) could be done here? That utility looks for a symbol on the function that has its already-provided promisified implementation, and transparently returns it if so. It only creates a new function if that symbol key isn't provided.
Could signal have something like that on it, either the promise itself, or a function to vend the promise, and then the aborted(..) util looks for that, and if it finds it, just returns, and if not, it attaches the event?
This approach would make it significantly easier for me to migrate CAF to using this util without introducing a major breaking change.
@getify thanks, opened whatwg/dom#946 I think we can ship aborted experimentally (if James/Anna/other Node people agree it's interesting to explore) regardless of the outcome of the standards effort.
I feel strongly that if we want to pursue a standards contribution rather than a Node.js utility we should not ship a experimental utility in the meantime but I am very interested what Jake/Domenic/Anne have to say.
Could signal have something like that on it, either the promise itself, or a function to vend the promise, and then the aborted(..) util looks for that, and if it finds it, just returns, and if not, it attaches the event?
I think it's worth exploring. I think one of the cool bits is that if it's a utility and we don't actually have to call addEventListener we can just bind the lifetime of the listener to the lifetime of the resource which would negate the need to actually remoe the listener.
Can you write a quick code example of what you mean here so I make sure we're on the same page?
In CAF, then, I could continue to create the promise myself, and instead of (or in addition to) attaching it as a .pr property, I could just set it on that symbol location.
The promise I vend in CAF could reject instead of fulfill, so that CAF's logic and use-cases are still preserved. But then Node could create a fulfilling-promise by default, if that's more desired.
BTW, the reason CAF needs to vend its own promise is not just that reject semantic... but also because CAF relies on being able to pass along a specific reason to that promise rejection, so that it's easy to detect in catch(..) handlers that the cancellation occured.
Definitely think some utility code would be helpful. I'd actually rather not optimize it for Promise.race() given how many times I see that pattern abuse in really fundamental ways.
Let's back up and review the requirements for a bit... what exactly is needed...
Determine if a signal is provided.
Determine if a signal has already been aborted (early bail out)
Attach an abort listener that cancels the expected action
Run the expected action
If, after the action is completed, the signal has been triggered, perform cleanup
Otherwise, remove the abort listener and continue...
Given that, a wrapper such as the following would work... (haven't tested and in a hurry typing this so may be errors but you should get the idea)
I'd actually rather not optimize it for Promise.race()
If it's not exposed as a promise, it won't be of any use to CAF (or other libs of the same vein), which has a few years head start on this effort here in terms of how it's modeled async cancellation via a promise attached to the abort signal, and the heavy use of Promise.race(..) to stop waiting on a promise when a cancellation is triggered.
Is there any part of the util.aborted(..) design that has been floated with the web platform standards such that we may have hope in the future of a cross-platform way of handling this promise-ified signal cancellation? I suppose it's not a difficult utility to partially polyfill (other than the weak event listener stuff), but it'd certainly be nice if there was a possible plan to standardize this.
Is there any part of the util.aborted(..) design that has been floated with the web platform standards such that we may have hope in the future of a cross-platform way of handling this promise-ified signal cancellation? I suppose it's not a difficult utility to partially polyfill (other than the weak event listener stuff), but it'd certainly be nice if there was a possible plan to standardize this.
I don't have time to engage in the capacity required for the standardization effort and my employer won't sponsor that work. I am happy to help with such an effort though.
Activity
benjamingr commentedon Feb 4, 2021
(I am happy to implement - though I haven't landing the weak event handlers PR yet because I am very hesitant about it)
getify commentedon Feb 4, 2021
The common usage pattern I anticipate for this util is:
FWIW, in CAF, the promise I create for an abort signal actually rejects when the token is aborted... this makes detection of the abort a little easier (like with
try..catcharound theawait). I think it would be more useful ifaborted(..)made a promise that rejects rather than resolves. If not, I just have to create some more machinery around it to not create breaking changes in CAF.In any case, I would envision shipping some sort of polyfill for this util along with CAF, because CAF is designed to work in both Node and the browser. So interop with the browser env is important here. Maybe it's a good idea to float such a util with WHATWG (or whoever) to see what their interest level is?
benjamingr commentedon Feb 4, 2021
@getify I think we'll use it a ton in core as well, we currently have a ton of code that looks like:
That would become:
Other than being significantly shorter the selling point for me here is that the code is significantly safer as well - since you don't have to remember to remove the listener to the signal.
Sure, I'll open an issue.
getify commentedon Feb 4, 2021
I'm also wondering if something like what was done with
util.promisify(..)could be done here? That utility looks for a symbol on the function that has its already-provided promisified implementation, and transparently returns it if so. It only creates a new function if that symbol key isn't provided.Could
signalhave something like that on it, either the promise itself, or a function to vend the promise, and then theaborted(..)util looks for that, and if it finds it, just returns, and if not, it attaches the event?This approach would make it significantly easier for me to migrate CAF to using this util without introducing a major breaking change.
benjamingr commentedon Feb 4, 2021
@getify thanks, opened whatwg/dom#946 I think we can ship
abortedexperimentally (if James/Anna/other Node people agree it's interesting to explore) regardless of the outcome of the standards effort.I feel strongly that if we want to pursue a standards contribution rather than a Node.js utility we should not ship a experimental utility in the meantime but I am very interested what Jake/Domenic/Anne have to say.
I think it's worth exploring. I think one of the cool bits is that if it's a utility and we don't actually have to call
addEventListenerwe can just bind the lifetime of the listener to the lifetime of the resource which would negate the need to actually remoe the listener.Can you write a quick code example of what you mean here so I make sure we're on the same page?
getify commentedon Feb 4, 2021
I was sorta envisioning logic like this:
In CAF, then, I could continue to create the promise myself, and instead of (or in addition to) attaching it as a
.prproperty, I could just set it on that symbol location.Then, CAF-using code would just look like:
...instead of needing the special casing like:
The promise I vend in CAF could reject instead of fulfill, so that CAF's logic and use-cases are still preserved. But then Node could create a fulfilling-promise by default, if that's more desired.
BTW, the reason CAF needs to vend its own promise is not just that reject semantic... but also because CAF relies on being able to pass along a specific reason to that promise rejection, so that it's easy to detect in
catch(..)handlers that the cancellation occured.jasnell commentedon Feb 4, 2021
Definitely think some utility code would be helpful. I'd actually rather not optimize it for
Promise.race()given how many times I see that pattern abuse in really fundamental ways.Let's back up and review the requirements for a bit... what exactly is needed...
Given that, a wrapper such as the following would work... (haven't tested and in a hurry typing this so may be errors but you should get the idea)
As I said, this is rough but something like this should work to address the common pattern.
getify commentedon Feb 4, 2021
If it's not exposed as a promise, it won't be of any use to CAF (or other libs of the same vein), which has a few years head start on this effort here in terms of how it's modeled async cancellation via a promise attached to the abort signal, and the heavy use of
Promise.race(..)to stop waiting on a promise when a cancellation is triggered.debadree25 commentedon Feb 2, 2023
Is there still interest in this? I would be interest in giving this a try
Also same question
benjamingr commentedon Feb 2, 2023
Feel free to pick this up!
lib: add aborted() utility function
lib: add aborted() utility function
getify commentedon Feb 7, 2023
Is there any part of the
util.aborted(..)design that has been floated with the web platform standards such that we may have hope in the future of a cross-platform way of handling this promise-ified signal cancellation? I suppose it's not a difficult utility to partially polyfill (other than the weak event listener stuff), but it'd certainly be nice if there was a possible plan to standardize this.debadree25 commentedon Feb 7, 2023
I think @benjamingr did float the idea of standardization?
benjamingr commentedon Feb 7, 2023
I don't have time to engage in the capacity required for the standardization effort and my employer won't sponsor that work. I am happy to help with such an effort though.
Personally, I am in favor of standardization.
debadree25 commentedon Feb 7, 2023
What would be required (have no idea about the processes involved 😅) would love to help
benjamingr commentedon Feb 14, 2023
Basically engage in the whatwg issue :) Join the chat whatwg has some nice folks
lib: add aborted() utility function
lib: add aborted() utility function