@webfill/async-context
v1.0.0
Published
🗺️ An experimental AsyncContext polyfill
Downloads
20
Maintainers
Readme
AsyncContext
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!
Installation
This package can be installed locally using npm, Yarn, pnpm, or your other favorite package manager of choice:
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:
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:
import {} from "https://esm.sh/@webfill/async-context";
import {} from "https://esm.run/@webfill/async-context";
Usage
This package exports the AsyncContext
namespace. To get started, you can
create a new AsyncContext.Variable
and use it in various places. When you use
the .run(value, f)
method, it will cascade that value throughout the entire
(possibly asynchronous) execution of any subsequent functions. Here's a quick
demo:
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 track
a Request
's ID across many different asynchronous functions without
resorting to "argument drilling":
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()} 👋`);
//=> 'Hello, 1 👋'
//=> 'Hello, 2 👋'
} else {
await doThing();
logError("Not found");
return new Response(`${id.get()} not found.`, { status: 404 });
//=> '1 not found.'
//=> '2 not found.'
}
}
Here's the example from the proposal for reference.
const asyncVar = new AsyncContext.Variable(); // Sets the current value to 'top', and executes the `main` 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; }