πΊοΈ An experimental AsyncContext polyfill
npm install @webfill/async-contextAsyncContext polyfill for Node.js and the browserπΊοΈ An [experimental AsyncContext] polyfill

β οΈ Experimental API \
π£ Uses Node.js' [AsyncLocalStorage] if possible \
π§
Works with Bun via [Zone.js] \
π¦ Works with Deno via [their Node.js compat layer] \
π Works in the browser via [Zone.js]!
This package can be installed locally using npm, [Yarn], [pnpm], or your other
favorite package manager of choice:
``sh`
npm install @webfill/async-context
If you're using Deno, you can install this package using the new [npm:
specifiers], or directly from a Deno-compatible npm CDN like [esm.sh]:
`js`
import {} from "npm:@webfill/async-context";
import {} from "https://esm.sh/@webfill/async-context";
If you want to use this package in the browser without needing a build tool to
bundle your npm dependencies, you can use an npm CDN like [esm.sh] or [jsDelivr]
to import it directly from a URL:
`js`
import {} from "https://esm.sh/@webfill/async-context";
import {} from "https://esm.run/@webfill/async-context";
This package exports the AsyncContext namespace. To get started, you canAsyncContext.Variable
create a new and use it in various places. When you use.run(value, f)
the method, it will cascade that value throughout the entire
(possibly asynchronous) execution of any subsequent functions. Here's a quick
demo:
`js
import AsyncContext from "@webfill/async-context";
const message = new AsyncContext.Variable({ defaultValue: "Hello" });
message.run("Hi", async () => {
await fetch("https://jsonplaceholder.typicode.com/todos/1");
console.log(message.get());
//=> "Hi"
});
message.run("Hey", () => {
setTimeout(() => {
console.log(message.get());
//=> "Hey"
}, 10);
});
console.log(message.get());
//=> "Hello"
`
For a more practical example, you could use an AsyncContext.Variable to trackRequest
a 's ID across many different asynchronous functions **without
resorting to "argument drilling"**:
`js
const id = new AsyncContext.Variable();
let i = 0;
globalThis.addEventListener("fetch", (event) => {
id.run(++i, () => {
event.respondWith(handleRequest(event.request));
});
});
function logError(message) {
// Note that this is two calls deep in an async chain! Yet we still get the
// correct ID that was set via 'id.run()'
console.error(id.get(), message);
//=> '1' 'Not found'
//=> '2' 'Not found'
}
async function handleRequest(request) {
if (request.url === "/") {
await doThing();
return new Response(Hello, ${id.get()} π);${id.get()} not found.
//=> 'Hello, 1 π'
//=> 'Hello, 2 π'
} else {
await doThing();
logError("Not found");
return new Response(, { status: 404 });`
//=> '1 not found.'
//=> '2 not found.'
}
}
Here's the example from the proposal for reference.
> `jsmain
> const asyncVar = new AsyncContext.Variable();
>
> // Sets the current value to 'top', and executes the function.`
> asyncVar.run("top", main);
>
> function main() {
> // AsyncContext.Variable is maintained through other platform queueing.
> setTimeout(() => {
> console.log(asyncVar.get()); // => 'top'
>
> asyncVar.run("A", () => {
> console.log(asyncVar.get()); // => 'A'
>
> setTimeout(() => {
> console.log(asyncVar.get()); // => 'A'
> }, randomTimeout());
> });
> }, randomTimeout());
>
> // AsyncContext.Variable runs can be nested.
> asyncVar.run("B", () => {
> console.log(asyncVar.get()); // => 'B'
>
> setTimeout(() => {
> console.log(asyncVar.get()); // => 'B'
> }, randomTimeout());
> });
>
> // AsyncContext.Variable was restored after the previous run.
> console.log(asyncVar.get()); // => 'top'
>
> // Captures the state of all AsyncContext.Variable's at this moment.
> const snapshotDuringTop = new AsyncContext.Snapshot();
>
> asyncVar.run("C", () => {
> console.log(asyncVar.get()); // => 'C'
>
> // The snapshotDuringTop will restore all AsyncContext.Variable to their snapshot
> // state and invoke the wrapped function. We pass a function which it will
> // invoke.
> snapshotDuringTop.run(() => {
> // Despite being lexically nested inside 'C', the snapshot restored us to
> // to the 'top' state.
> console.log(asyncVar.get()); // => 'top'
> });
> });
> }
>
> function randomTimeout() {
> return Math.random() * 1000;
> }
>
— [Proposed Solution | Async Context for JavaScript]
[experimental AsyncContext]: https://github.com/tc39/proposal-async-context#readmeAsyncLocalStorage
[]: https://nodejs.org/api/async_context.html#async_context_class_asynclocalstoragenpm:` specifiers]: https://deno.land/manual/node/npm_specifiers
[their Node.js compat layer]: https://github.com/denoland/deno/tree/main/ext/node/polyfills#readme
[Zone.js]: https://www.npmjs.com/package/zone.js
[Proposed Solution | Async Context for JavaScript]: https://github.com/tc39/proposal-async-context#proposed-solution
[
[esm.sh]: https://esm.sh/
[jsDelivr]: https://www.jsdelivr.com/esm
[Yarn]: https://yarnpkg.com/
[pnpm]: https://pnpm.io/