Continuation-Local Storage (Hooked)

4.2.2 · active · verified Sun Apr 19

cls-hooked is a Node.js library that provides Continuation-Local Storage (CLS), a mechanism akin to thread-local storage but adapted for Node.js's asynchronous, callback-based execution model. It enables developers to maintain and implicitly pass contextual data across asynchronous operations without explicit parameter passing. The current stable version, 4.2.2, primarily targets Node.js versions `^4.7 || >=6.9 || >=7.3 || >=8.2.1`. Its key differentiator lies in its implementation, which leverages Node.js's internal, lower-level APIs: `AsyncWrap` for Node.js versions prior to v8 (an unofficial API), and the `async_hooks` API for Node.js v8.2.1 and newer (which is considered experimental). This approach distinguishes it from its predecessor, `node-continuation-local-storage`, which relied on the now-deprecated `async-listener`. The project's release cadence is largely influenced by the evolution and stability of these underlying Node.js APIs, aiming to provide a robust CLS implementation despite the experimental nature of its core dependencies.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to create and manage continuation-local storage contexts using `cls-hooked`'s `createNamespace`, `run`, `set`, and `get` methods, including persistence across asynchronous operations and nested contexts.

const cls = require('cls-hooked');
const session = cls.createNamespace('my-app-session');

console.log('--- Starting CLS Example ---');

function performAsyncTask(callback) {
  setTimeout(() => {
    // Simulate some async operation that takes time
    console.log(`[Async Task] Inside async operation. Context user: ${session.get('user') || 'N/A'}`);
    callback();
  }, 50);
}

session.run(() => {
  const requestId = 'req-123';
  session.set('requestId', requestId);
  session.set('user', 'Alice');
  console.log(`[Main Thread] Initial context set. Request ID: ${session.get('requestId')}, User: ${session.get('user')}`);

  performAsyncTask(() => {
    // Even after an async operation, the context should persist
    console.log(`[Callback 1] After async task. Request ID: ${session.get('requestId')}, User: ${session.get('user')}`);

    // Call another function that accesses the context
    anotherFunction();

    // Now, run another context nested within
    session.run(() => {
      session.set('user', 'Bob'); // This should be specific to the nested run
      console.log(`[Nested Context] Inside nested run. Request ID: ${session.get('requestId')}, User: ${session.get('user')}`);
      performAsyncTask(() => {
        console.log(`[Nested Callback] After nested async task. Request ID: ${session.get('requestId')}, User: ${session.get('user')}`);
      });
    });

    console.log(`[Callback 1 Cont.] After nested context block. User: ${session.get('user')} (should be Alice again)`);
  });
});

function anotherFunction() {
  console.log(`[Another Function] Accessing context. Request ID: ${session.get('requestId')}, User: ${session.get('user')}`);
}

// Outside any 'run' block, the context should not be available
setTimeout(() => {
  console.log(`[Outside Context] After all operations. Request ID: ${session.get('requestId') || 'N/A'}, User: ${session.get('user') || 'N/A'}`);
}, 150);

view raw JSON →