I have some 'Higher Order functions' that can create me a fetch function with some extra behavior on it.
Some of them can abort the request.
Here a simplified example with a timeout that can abort:
const fetch = pipe(
window.fetch,
withTimeout({ timeoutInMs: 100 }), // 1 fetch can take max 100ms
withRetry({ retryCount: 3, delayInMs: 100 }), // Retry 3x with a delay of 100ms
withTimeout({ timeoutInMs: 400 }), // all fetches (with retry) can take max 400ms
)
_In a real application, the second withTimeout could also be a withCancel function._
The first withTimeout will create an AbortController instance and set the signal property on the requestInit argument of fetch,
The second withTimeout will also create an AbortController and set the signal to the requestInit argument. This gives a problem because there already is a signal.
I could create for this sample one AbortController outside the withTimeout's and pass it to both withTimeout's as argument, but in a real application the enhancing of a fetch function can be done on different layers in the application. This causes other problems:
abortController instance to withTimeout.abortController instance is used for this fetch and have acces to it.and this makes the code more dirty in my opinion.
What would be great is a way to pass multiple abort signals to a fetch function, some idea's:
fetch also accept an array of abortSignalsAbortSignal's into one AbortSignalGiven that AbortSignal has an event and can be constructed via AbortController, you should be able to create your own and do whatever, no?
I'm unclear on exactly the connection to the OP's problem, but the OP's suggestion at the bottom of their post of
provide a way to combine/merge two AbortSignal's into one AbortSignal
is definitely something we considered. I believe we just put it off to get v1 out the door. An AbortSignal.any([signal1, signal2, ...]) that aborts if any of the inputs are aborted makes sense to me. (Name bikeshedding possible.)
@annevk I think you are right, I will try it on another way...
I would like to create an abortable chain of actions.
I think I will need to create my own 'aborter' object that can have a hierarchical structure where a parent will abort its children.
At the leafs of the tree where a fetch is, it should create an AbortController and pass the signal to the fetch.
I will rethink it and will post my workaround if I can get it to work,
Thanks
fwiw
function anySignal(signals) {
const controller = new AbortController();
function onAbort() {
controller.abort();
// Cleanup
for (const signal of signals) {
signal.removeEventListener('abort', onAbort);
}
}
for (const signal of signals) {
if (signal.aborted) {
onAbort();
break;
}
signal.addEventListener('abort', onAbort);
}
return controller.signal;
}
Useful function!
Small improvement, if one of the signals is already aborted, I would prefer to return an (or the) aborted signal instead of undefined.
if (signal.aborted) {
onAbort();
return signal;
}
My bad, I should have used break, not return. I've updated the snippet.
FYI: I added my experiments with abortable fetch on https://github.com/jovdb/fetch-hof
and in the end, I didn't need the multiple AbortSignals on fetch.
I have used the CancelToken proposal in my experiment.
I pass around this generic CancelToken from function to function, where each function can subscribe to the CancelToken and pass a new CancelToken to it children (if needed: like withTimeout)
The withFetchAbort higher order function will create an AbortController and pass the AbortSignal to fetch, it subscribes to the CancelToken. If the CancelToken is cancelled, it will call abortController.abort() to cancel the fetch.
@benjamingr this issue might be of interest to you. I'm folding it into https://github.com/whatwg/dom/issues/920 and hope that's acceptable to everyone.
Most helpful comment
fwiw