Backend Development 17 min read

Understanding Node.js AsyncHooks and AsyncLocalStorage for Asynchronous Resource Tracking

This article explains Node.js async_hooks and AsyncLocalStorage APIs, describing asynchronous resource concepts, lifecycle hooks, usage scenarios, performance impact, and practical code examples for tracing async call chains, custom async resources, and integrating context propagation in backend applications.

ByteFE
ByteFE
ByteFE
Understanding Node.js AsyncHooks and AsyncLocalStorage for Asynchronous Resource Tracking

What Are Asynchronous Resources

In Node.js an asynchronous resource is any object that has an associated callback which may be invoked one or more times (e.g., fs.open creates an FSReqCallback , net.createServer creates a TCP object). AsyncHooks abstracts these resources without caring about their specific type.

Why Track Asynchronous Resources

Because Node.js uses an event‑loop based non‑blocking I/O model, the original caller of an asynchronous operation is lost by the time its callback runs, making it hard to reconstruct call chains.

Scenario 1

const fs = require('fs');
function callback(err, data) {
  console.log('callback', data);
}
fs.readFile('a.txt', callback);
console.log('after a');
fs.readFile('b.txt', callback);
console.log('after b');
// output: after a, after b, callback undefined, callback undefined

The logs show that we cannot tell which callback belongs to which readFile call.

Scenario 2

function main() {
  setTimeout(() => {
    throw Error(1);
  }, 0);
}
main();
// Error stack is incomplete because the async boundary is lost.

Both examples illustrate the need for a mechanism that preserves the async execution context.

AsyncHooks Overview

AsyncHooks provides lifecycle callbacks ( init , before , after , destroy , promiseResolve ) that are triggered for every asynchronous resource.

const async_hooks = require('async_hooks');
const asyncHook = async_hooks.createHook({ init, before, after, destroy, promiseResolve });
asyncHook.enable();

Hook Callback Parameters

asyncId : unique identifier of the resource (starts at 1).

type : string describing the resource type (e.g., FSREQCALLBACK , TCPWRAP ).

triggerAsyncId : asyncId of the resource that created the current one.

resource : the actual object representing the async resource.

Performance Impact

Enabling all hooks adds noticeable overhead. Benchmarks on Node v14.16.0 show a drop from ~11 734 req/s (regular) to ~6 250 req/s (full hooks) for a typical HTTP server.

AsyncResource

Custom asynchronous resources can be created by extending AsyncResource . This allows manual control over the async context.

const { AsyncResource } = require('async_hooks');
class MyResource extends AsyncResource {
  constructor() { super('my-resource'); }
  close() { this.emitDestroy(); }
}

AsyncLocalStorage

AsyncLocalStorage provides a simple API to store data throughout an async call chain, similar to thread‑local storage in other languages.

const http = require('http');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
let idSeq = 0;
http.createServer((req, res) => {
  asyncLocalStorage.run(idSeq++, () => {
    console.log('start', asyncLocalStorage.getStore());
    setImmediate(() => {
      console.log('finish', asyncLocalStorage.getStore());
      res.end();
    });
  });
}).listen(8080);

Implementation Details

Internally AsyncLocalStorage registers a tiny async hook that propagates a hidden store from the current resource to any newly created child resource, allowing getStore() to retrieve the value at any point.

Use Cases

Continuation‑local storage (CLS) implementations such as cls‑hooked and asynchronous‑local‑storage .

Clinic.js’s Bubbleprof uses AsyncHooks + Error.captureStackTrace to visualise async call graphs.

Frameworks like Midway and Farrow leverage AsyncLocalStorage to share request‑scoped context without passing explicit parameters.

References

Node.js official async_hooks documentation.

bmeurer/async-hooks-performance-impact (GitHub).

Making async_hooks fast (enough) – internal design notes.

Various blog posts and issue discussions linked in the original article.

backendperformanceNode.jsasync_hooksAsyncLocalStorage
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.