I'm sure this idea has come up before, but I couldn't find any existing content on it, so I figured I'd put together an issue.
I want to explore the idea of having standardized AsyncContext.Variable objects for specific use cases, much like how Symbol can create arbitrary symbols, but also contains a list of Symbol with specific semantics (ex. Symbol.iterator). Can we define specific AsyncContext variables for particular use cases which might be leveraged by standard implementations?
The main use case I'm thinking of is AbortSignal. Currently, developers much manually pass through AbortSignal into all relevant async APIs.
async function parent({ signal }: { signal?: AbortSignal } = {}): Promise<void> {
await child({ signal });
}
async function child({ signal }: { signal?: AbortSignal } = {}): Promise<void> {
await grandchild({ signal });
}
async function grandchild({ signal }: { signal?: AbortSignal } = {}): Promise<void> {
const res1 = await fetch('/one', { signal });
const res2 = await fetch('/two', { signal });
// ...
}
To do this correctly, every async operation needs to accept a signal as an input and properly pass it through to all async functions they call. That's a lot of boilerplate and it's easy to forget.
I'd like to propose a standardized AsyncContext.signal value. In practice, this is just a standard AsyncContext.Variable containing an optional AbortSignal and defaulting to undefined.
AsyncContext.signal = new AsyncContext.Variable<AbortSignal | undefined>(undefined);
Then, anyone can read/write to this context rather than passing through signal in function parameters.
async function parent(): Promise<void> {
await child();
}
async function child(): Promise<void> {
await grandchild();
}
async function grandchild(): Promise<void> {
const signal = AsyncContext.abort.get(); // Get the signal.
// Use it.
if (signal) {
if (signal.aborted) return;
signal.addEventListener('abort', () => { /* ... */ }, { once: true });
}
// ...
}
const signal = AbortSignal.timeout(1_000); // Create an `AbortSignal`.
await AsyncContext.abort.run(signal, () => parent()); // Automatically times out after 1sec.
Now anyone could define their own AsyncContext.Variable<AbortSignal | undefined> for this purpose, however by having a standard location for it, we get two additional benefits:
AsyncContext.signal can be shared across libraries and more consistently used in the JavaScript ecosystem (different libraries don't need to define their own variable).
Standard functions can use AsyncContext.signal as well.
Expanding on 2., what if fetch was aware of AsyncContext.signal and listened to it? Then, the following could work:
// No `AbortSignal` anywhere in the function definitions!
async function parent(): Promise<void> {
await child();
}
async function child(): Promise<void> {
await grandchild();
}
async function grandchild(): Promise<void> {
const res1 = await fetch('/one'); // Inherits `AsyncContext.signal`.
const res2 = await fetch('/two'); // Inherits `AsyncContext.signal`.
// ...
}
// Run `parent()` and timeout after 1sec.
await AsyncContext.signal.run(AbortSignal.timeout(1_000), () => parent());
fetch could read AsyncContext.signal and automatically cancel the active request when it aborts!
This feels very useful to me and fixes a lot of the ecosystem problems with AbortSignal today. Not requiring developers to understand and design this concept into their APIs feels like a huge win. Aborting async operations basically "just works".
There are probably other use cases which might benefit from a standardized AsyncContext.Variable, this is just the most obvious one to me. This particular one is probably more of a follow-up standard after AsyncContext lands on its own, but I think it's worth mentioning here to foster some discussion about the use case and to use this as additional motivation for why AsyncContext could be useful.
I'm sure this idea has come up before, but I couldn't find any existing content on it, so I figured I'd put together an issue.
I want to explore the idea of having standardized
AsyncContext.Variable
objects for specific use cases, much like howSymbol
can create arbitrary symbols, but also contains a list ofSymbol
with specific semantics (ex.Symbol.iterator
). Can we define specificAsyncContext
variables for particular use cases which might be leveraged by standard implementations?The main use case I'm thinking of is
AbortSignal
. Currently, developers much manually pass throughAbortSignal
into all relevant async APIs.To do this correctly, every async operation needs to accept a signal as an input and properly pass it through to all async functions they call. That's a lot of boilerplate and it's easy to forget.
I'd like to propose a standardized
AsyncContext.signal
value. In practice, this is just a standardAsyncContext.Variable
containing an optionalAbortSignal
and defaulting toundefined
.Then, anyone can read/write to this context rather than passing through
signal
in function parameters.Now anyone could define their own
AsyncContext.Variable<AbortSignal | undefined>
for this purpose, however by having a standard location for it, we get two additional benefits:AsyncContext.signal
can be shared across libraries and more consistently used in the JavaScript ecosystem (different libraries don't need to define their own variable).AsyncContext.signal
as well.Expanding on 2., what if
fetch
was aware ofAsyncContext.signal
and listened to it? Then, the following could work:fetch
could readAsyncContext.signal
and automatically cancel the active request when it aborts!This feels very useful to me and fixes a lot of the ecosystem problems with
AbortSignal
today. Not requiring developers to understand and design this concept into their APIs feels like a huge win. Aborting async operations basically "just works".There are probably other use cases which might benefit from a standardized
AsyncContext.Variable
, this is just the most obvious one to me. This particular one is probably more of a follow-up standard afterAsyncContext
lands on its own, but I think it's worth mentioning here to foster some discussion about the use case and to use this as additional motivation for whyAsyncContext
could be useful.