als-browser
v1.0.1
Published
Browser polyfill for Node.js AsyncLocalStorage
Maintainers
Readme
als-browser
Browser-compatible polyfill for Node.js's AsyncLocalStorage API. This package
enables async context propagation in browser environments by patching common
async browser APIs.
Features
- Full
AsyncLocalStorageAPI compatibility - Automatic patching of browser async APIs
- Zero dependencies (dev dependencies only)
- TypeScript support with full type definitions
- ESM and CommonJS builds
- Comprehensive test coverage
Installation
npm install als-browser
# or
pnpm add als-browser
# or
yarn add als-browserUsage
import { AsyncLocalStorage } from 'als-browser';
// Create a storage instance
const requestContext = new AsyncLocalStorage<{ userId: string }>();
// Use run() to execute code in a context
requestContext.run({ userId: '123' }, () => {
console.log(requestContext.getStore()); // { userId: '123' }
// Context is preserved through setTimeout
setTimeout(() => {
console.log(requestContext.getStore()); // { userId: '123' }
}, 100);
});API
AsyncLocalStorage<T>
constructor(options?)
const store = new AsyncLocalStorage<T>({
defaultValue?: T, // Optional default value
name?: string // Optional name for debugging
});run(data, callback, ...args)
Run a function in a new async context with the given data.
const result = store.run(myData, () => {
// Your code here
return store.getStore(); // Returns myData
});getStore()
Get the current value from this store.
const currentValue = store.getStore();enterWith(data)
Enter a new async context with the given data (no callback).
store.enterWith(myData);
console.log(store.getStore()); // myDataexit(callback, ...args)
Run a function with the store value set to undefined.
store.exit(() => {
console.log(store.getStore()); // undefined
});disable()
Remove this store from the current async context.
store.disable();Static: bind(fn)
Bind a function to the current async context.
const boundFn = AsyncLocalStorage.bind(() => {
return store.getStore();
});Static: snapshot()
Capture the current async context and return a function that can restore it.
const snapshot = AsyncLocalStorage.snapshot();
snapshot(() => {
// Runs in captured context
});Manual Context Propagation
For advanced use cases like code transformers or custom async instrumentation, you can manually capture and restore async context around await points.
capture(container, promise)
Capture the current async context frame before an await and store it in a container object.
import { capture, restore, SnapshotContainer } from 'als-browser';
const container: SnapshotContainer = {};
const result = restore(container, await capture(container, promise));restore(container, value)
Restore the async context frame after an await from the container object.
// Transform: await foo()
// Into: restore(container, await capture(container, foo()))
const container: SnapshotContainer = {};
store.run(myData, async () => {
// Manually preserve context across await
restore(container, await capture(container, asyncOperation()));
console.log(store.getStore()); // myData is preserved
});These functions are primarily useful for:
- Code transformers/compilers that automatically instrument async functions
- Custom async context tracking systems
- Debugging and understanding async context flow
Note: For normal application code, prefer using the automatic patches or AsyncLocalStorage.bind()/snapshot().
Patched Browser APIs
The following browser APIs are automatically patched to preserve async context:
Timers
setTimeoutsetIntervalsetImmediate(if available)
Animation
requestAnimationFramerequestIdleCallback
Network
XMLHttpRequestevent handlers (addEventListener and on* properties)
How It Works
This package implements Node.js's AsyncContextFrame model adapted for browsers:
- AsyncContextFrame: A Map-based storage for async context, stored in a module-level variable
- AsyncLocalStorage: The main API that stores and retrieves values from the current frame
- Browser API Patches: Automatically wraps callbacks to preserve context across async boundaries
The implementation replaces Node.js's V8 embedder data APIs with a simple module-level variable, making it work in any JavaScript environment.
Limitations
- Promise-based APIs: This package does not automatically patch promise-based APIs like
fetch(). For those, you need to manually propagate context usingAsyncLocalStorage.bind()orAsyncLocalStorage.snapshot(). - EventTarget.addEventListener: Only
XMLHttpRequestis patched. Other event targets may need manual context propagation. - Module-level state: The context is stored in a module-level variable, which means it's shared across all code in the same JavaScript realm.
Example: Request Tracing
import { AsyncLocalStorage } from 'als-browser';
const requestId = new AsyncLocalStorage<string>();
function generateId() {
return Math.random().toString(36).slice(2);
}
function log(message: string) {
const id = requestId.getStore() || 'no-context';
console.log(`[${id}] ${message}`);
}
// Start a request
requestId.run(generateId(), async () => {
log('Request started');
// Context preserved through setTimeout
setTimeout(() => {
log('Async operation 1');
}, 100);
// Context preserved through requestAnimationFrame
requestAnimationFrame(() => {
log('Animation frame');
});
// For fetch, manually bind
const boundHandler = AsyncLocalStorage.bind(async () => {
const response = await fetch('/api/data');
log('Fetch completed');
return response.json();
});
await boundHandler();
});Testing
# Run tests
pnpm test
# Build
pnpm build
# Type check
pnpm typecheckLicense
MIT
Credits
This implementation is based on Node.js's AsyncLocalStorage and AsyncContextFrame APIs:
lib/internal/async_context_frame.jslib/internal/async_local_storage/async_context_frame.js
