Skip to main content
Async context tracking APIs are stable and recommended for production use. They provide a performant and memory-safe way to track context across asynchronous operations.

Overview

The async context tracking classes allow you to store data throughout the lifetime of a web request or any other asynchronous duration, similar to thread-local storage in other languages.
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks';
// or
const { AsyncLocalStorage, AsyncResource } = require('node:async_hooks');

AsyncLocalStorage

The AsyncLocalStorage class creates stores that stay coherent through asynchronous operations.

Constructor

new AsyncLocalStorage([options])

Creates a new instance of AsyncLocalStorage.
options
object
Optional configuration
defaultValue
any
The default value when no store is provided
name
string
A name for the AsyncLocalStorage instance
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage({
  defaultValue: { userId: 'anonymous' },
  name: 'requestContext'
});

Instance Methods

run(store, callback, …args)

Runs a function synchronously within a context and returns its value.
store
any
required
The store value to use for this context
callback
function
required
The function to run within the context
args
any
Optional arguments to pass to the callback
return
any
The return value of the callback function
import http from 'node:http';
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

function logWithId(msg) {
  const id = asyncLocalStorage.getStore();
  console.log(`${id !== undefined ? id : '-'}:`, msg);
}

let idSeq = 0;
http.createServer((req, res) => {
  asyncLocalStorage.run(idSeq++, () => {
    logWithId('start');
    setImmediate(() => {
      logWithId('finish');
      res.end();
    });
  });
}).listen(8080);

getStore()

Returns the current store value.
return
any
The current store, or undefined if called outside of run() or enterWith()
const store = asyncLocalStorage.getStore();
if (store) {
  console.log('User ID:', store.userId);
}

enterWith(store)

Experimental - Transitions into the context for the remainder of the current synchronous execution.
store
any
required
The store value to use
const store = { id: 1 };
asyncLocalStorage.enterWith(store);
asyncLocalStorage.getStore(); // Returns { id: 1 }

someAsyncOperation(() => {
  asyncLocalStorage.getStore(); // Still returns { id: 1 }
});

exit(callback, …args)

Experimental - Runs a function outside of the current context.
callback
function
required
Function to run outside the context
asyncLocalStorage.run(new Map(), () => {
  asyncLocalStorage.getStore(); // Returns the Map
  
  asyncLocalStorage.exit(() => {
    asyncLocalStorage.getStore(); // Returns undefined
  });
  
  asyncLocalStorage.getStore(); // Returns the Map again
});

disable()

Experimental - Disables the instance. All subsequent calls to getStore() return undefined.

Static Methods

AsyncLocalStorage.bind(fn)

Binds the given function to the current execution context.
fn
function
required
The function to bind
return
function
A new function that calls fn within the captured context

AsyncLocalStorage.snapshot()

Captures the current execution context and returns a function wrapper.
return
function
A function that accepts a function and runs it in the captured context
const asyncLocalStorage = new AsyncLocalStorage();
const runInAsyncScope = asyncLocalStorage.run(123, () => 
  AsyncLocalStorage.snapshot()
);

const result = asyncLocalStorage.run(321, () => 
  runInAsyncScope(() => asyncLocalStorage.getStore())
);
console.log(result);  // Returns 123

Properties

name

name
string
The name of the AsyncLocalStorage instance if provided

Complete Example: HTTP Request Context

import http from 'node:http';
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

function logWithId(msg) {
  const id = asyncLocalStorage.getStore();
  console.log(`${id !== undefined ? id : '-'}:`, msg);
}

let idSeq = 0;
http.createServer((req, res) => {
  asyncLocalStorage.run(idSeq++, () => {
    logWithId('start');
    // Imagine any chain of async operations here
    setImmediate(() => {
      logWithId('finish');
      res.end();
    });
  });
}).listen(8080);

http.get('http://localhost:8080');
http.get('http://localhost:8080');
// Prints:
//   0: start
//   0: finish
//   1: start
//   1: finish

AsyncResource

The AsyncResource class is designed to be extended by embedders’ async resources to trigger lifetime events.

Constructor

new AsyncResource(type[, options])

Creates a new AsyncResource.
type
string
required
The type of async event
options
object
Configuration options
triggerAsyncId
number
The ID of the execution context that created this async event. Default: executionAsyncId()
requireManualDestroy
boolean
default:"false"
Disables automatic emitDestroy when garbage collected
import { AsyncResource, executionAsyncId } from 'node:async_hooks';

const asyncResource = new AsyncResource(
  'MyResource',
  { triggerAsyncId: executionAsyncId() }
);

Instance Methods

runInAsyncScope(fn, thisArg, …args)

Runs a function in the execution context of the async resource.
fn
function
required
The function to call
thisArg
any
The receiver for the function call
args
any
Optional arguments to pass to the function
class DBQuery extends AsyncResource {
  constructor(db) {
    super('DBQuery');
    this.db = db;
  }

  getInfo(query, callback) {
    this.db.get(query, (err, data) => {
      this.runInAsyncScope(callback, null, err, data);
    });
  }

  close() {
    this.db = null;
    this.emitDestroy();
  }
}

emitDestroy()

Call all destroy hooks. Should only be called once.
return
AsyncResource
Reference to asyncResource

asyncId()

Returns the unique ID assigned to the resource.
return
number
The unique asyncId

triggerAsyncId()

Returns the trigger ID passed to the constructor.
return
number
The same triggerAsyncId passed to the constructor

Static Methods

AsyncResource.bind(fn[, type[, thisArg]])

Binds the given function to the current execution context.
fn
function
required
The function to bind
type
string
Optional name for the underlying AsyncResource
thisArg
any
The receiver for the function

bind(fn[, thisArg])

Instance method to bind a function to this AsyncResource’s scope.

Worker Thread Pool Example

Using AsyncResource to properly track async operations in a worker pool:
import { AsyncResource } from 'node:async_hooks';
import { EventEmitter } from 'node:events';
import { Worker } from 'node:worker_threads';

const kTaskInfo = Symbol('kTaskInfo');
const kWorkerFreedEvent = Symbol('kWorkerFreedEvent');

class WorkerPoolTaskInfo extends AsyncResource {
  constructor(callback) {
    super('WorkerPoolTaskInfo');
    this.callback = callback;
  }

  done(err, result) {
    this.runInAsyncScope(this.callback, null, err, result);
    this.emitDestroy();
  }
}

export default class WorkerPool extends EventEmitter {
  constructor(numThreads) {
    super();
    this.numThreads = numThreads;
    this.workers = [];
    this.freeWorkers = [];
    this.tasks = [];

    for (let i = 0; i < numThreads; i++)
      this.addNewWorker();

    this.on(kWorkerFreedEvent, () => {
      if (this.tasks.length > 0) {
        const { task, callback } = this.tasks.shift();
        this.runTask(task, callback);
      }
    });
  }

  addNewWorker() {
    const worker = new Worker(new URL('task_processor.js', import.meta.url));
    worker.on('message', (result) => {
      worker[kTaskInfo].done(null, result);
      worker[kTaskInfo] = null;
      this.freeWorkers.push(worker);
      this.emit(kWorkerFreedEvent);
    });
    worker.on('error', (err) => {
      if (worker[kTaskInfo])
        worker[kTaskInfo].done(err, null);
      else
        this.emit('error', err);
      this.workers.splice(this.workers.indexOf(worker), 1);
      this.addNewWorker();
    });
    this.workers.push(worker);
    this.freeWorkers.push(worker);
    this.emit(kWorkerFreedEvent);
  }

  runTask(task, callback) {
    if (this.freeWorkers.length === 0) {
      this.tasks.push({ task, callback });
      return;
    }

    const worker = this.freeWorkers.pop();
    worker[kTaskInfo] = new WorkerPoolTaskInfo(callback);
    worker.postMessage(task);
  }

  close() {
    for (const worker of this.workers) worker.terminate();
  }
}

Usage with async/await

When using async functions, use run() to ensure the store is properly scoped:
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function fn() {
  await asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('key', value);
    return foo(); // The return value of foo will be awaited
  });
}

Troubleshooting: Context Loss

If context is lost in callback-based APIs:
  1. Promisify callback APIs using util.promisify()
  2. Use AsyncResource for custom thenable implementations
  3. Debug by logging asyncLocalStorage.getStore() after suspicious calls
import { promisify } from 'node:util';
import { AsyncResource } from 'node:async_hooks';

// Option 1: Promisify
const readFileAsync = promisify(fs.readFile);

// Option 2: Use AsyncResource
class CustomAsync extends AsyncResource {
  constructor() {
    super('CustomAsync');
  }
  
  doWork(callback) {
    someAsyncOp((err, result) => {
      this.runInAsyncScope(callback, null, err, result);
    });
  }
}

Integration with EventEmitter

Bind event listeners to the correct execution context:
import { createServer } from 'node:http';
import { AsyncResource } from 'node:async_hooks';

const server = createServer((req, res) => {
  req.on('close', AsyncResource.bind(() => {
    // Execution context is bound to the outer scope
  }));
  
  req.on('close', () => {
    // Execution context is bound to where 'close' emits
  });
  
  res.end();
}).listen(3000);

Best Practices

Performance Tips
  • Prefer run() over enterWith() for better scoping control
  • Use AsyncLocalStorage instead of manually tracking with AsyncResource for simple use cases
  • Remember that each AsyncLocalStorage instance maintains independent storage
Memory Management
  • Call disable() when AsyncLocalStorage is no longer needed
  • Remember to call emitDestroy() on AsyncResource instances
  • Stores are garbage collected with their corresponding async resources