Mastering Request Scope in Node.js with Async Hooks and AsyncLocalStorage
Learn how to implement request-scoped data in Node.js by leveraging Async Hooks and AsyncLocalStorage, covering use cases, trace ID management, code examples, and best practices for building robust, session-level isolation in backend services.
Understanding Request Scope in Web Services
Request scope refers to a lifecycle that lasts for a single session; a new request scope is created for each session and destroyed afterward, with isolation between sessions.
Use Cases
Recording request trace information is a classic scenario. Before a request starts, a trace ID is generated and all service calls, latency, and response data are attached to this trace, forming a complete call chain. This enables aggregation of scattered system calls and rapid problem analysis even in complex internal call graphs.
Image source: Best Practices for Building a Three‑Dimensional Monitoring System (https://cn.aliyun.com/aliware/news/monitoringsolution)
Implementation in Node.js
The overall call chain revolves around a TraceId, which must be globally accessible and have a request‑scope lifecycle, being unique per request.
Globally accessible: each service must obtain the current TraceId before invocation.
Lifecycle: the TraceId lives at request‑scope level; the same request gets a unique TraceId, different requests get different IDs.
Node.js offers several ways to satisfy these requirements:
Manual passing: provide TraceId as a function argument.
Middleware mounting: use frameworks like Midway or Koa to attach TraceId to the request context and retrieve it manually.
Both approaches work but have drawbacks: manual passing is cumbersome, while middleware depends heavily on framework capabilities.
We therefore recommend using Async Hooks.
Async Hooks
Async Hooks, introduced in Node.js 8.x, is a native module for tracking the lifecycle of asynchronous resources. It provides APIs to create a set of hooks.
Type definitions for creation
Key parameters:
asyncId: automatically incremented, globally unique identifier for the async resource.
type: type of async resource (e.g., FSEVENTWRAP, FSREQCALLBACK, Timeout).
triggerAsyncId: identifier of the async resource that created the current one.
resource: the actual resource instance.
Async Hooks also offers convenient APIs to obtain the current asyncId and triggerAsyncId.
Demo
Creating and enabling the hooks starts listening to async events.
Demo illustration:
Result illustration:
The global fs.open event shows that fs.open.triggerAsyncId equals global.asyncId.
Implementing Request Scope
Async Hooks provide two crucial pieces of information:
asyncId: automatically incremented, globally unique.
triggerAsyncId: indicates which async resource created the current one.
Using triggerAsyncId, we can infer the call chain, and because asyncId is unique, each function call gets a distinct identifier.
Thus, during each asynchronous call we generate a unique call chain, which is the key to request scope.
Implementation steps:
Create a request scope and store needed data within it.
Functions can access this data during execution.
The data lives at session level, isolated between sessions.
By leveraging asyncId and triggerAsyncId, the implementation is straightforward.
Specific Implementation
Below is the concrete implementation:
Output after execution:
This approach fully utilizes Async Hooks to achieve request scope, and it is simple to use in real projects.
AsyncLocalStorage
In the demo, AsyncLocalStorage is used as the name. It is a feature recently added to Node.js based on Async Hooks, providing the same request‑scope capability.
For more details, refer to the official Node.js documentation (v13.11.0).
References
Async Hooks official docs: https://nodejs.org/api/async_hooks.html
async-hooks: introduce async-storage API: https://github.com/nodejs/node/pull/26540
node-request-context: https://github.com/guyguyon/node-request-context
Learning async-hooks in Node.js: https://zhuanlan.zhihu.com/p/53036228
Best practices for building a three‑dimensional monitoring system: https://cn.aliyun.com/aliware/news/monitoringsolution
Node Underground
No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
