cls-hooked

repository·master·Indexed 21 days ago

https://github.com/jeff-lewis/cls-hooked

A Continuation-Local Storage (CLS) implementation for Node.js (>= 4.7.0) that uses AsyncWrap or async_hooks to store and access variables scoped to specific asynchronous execution chains. It provides a Namespace class API for managing contexts via methods like run(), set(), and get(), and supports binding EventEmitters to ensure context propagation across asynchronous boundaries.

Tokens
2K
Snippets
4
Records
8
Agent score
23%

What's inside cls-hooked

  1. What is Continuation-Local Storage (Hooked)?

    master

    Continuation-local storage works similarly to thread-local storage in threaded programming, but is designed for Node.js's asynchronous, callback-based model. It allows you to set and retrieve values that are scoped to the lifetime of a specific chain of function calls (a 'continuation').

    Values set in a namespace are accessible across synchronous and asynchronous boundaries, including process.nextTick, timers (setImmediate, setTimeout, setInterval), and asynchronous native functions (e.g., fs, dns, crypto).

    A common use case is replacing the practice of attaching properties to request or response objects in HTTP handlers with a scoped variable that is accessible throughout the entire request lifecycle.

  2. How Namespace nesting and context propagation works

    master

    Values are grouped into Namespaces. To ensure values are scoped correctly, you must use namespace.run() or namespace.bind() to define the boundaries of a continuation chain.

    When you nest calls to .run(), each nested context creates its own copy of the values from the parent context. This allows child calls to modify their own context without overwriting the parent's values. Once a nested context finishes, the execution returns to the parent context's state.

    Key Behavior:

    • namespace.run(callback): Creates a new context. The callback receives the new context as an argument.
    • Nested .run() calls: Create child contexts that inherit from the parent but can diverge.
    • Asynchronous completion: When a nested asynchronous operation finishes and the context is no longer active, execution reverts to the parent context.
    var createNamespace = require('cls-hooked').createNamespace;
    
    var writer = createNamespace('writer');
    writer.run(function () {
      writer.set('value', 0);
      requestHandler();
    });
    
    function requestHandler() {
      writer.run(function(outer) {
        // writer.get('value') returns 0
        // outer.value is 0
        writer.set('value', 1);
        // writer.get('value') returns 1
        // outer.value is 1
        process.nextTick(function() {
          // writer.get('value') returns 1
          // outer.value is 1
          writer.run(function(inner) {
            // writer.get('value') returns 1
            // outer.value is 1
            // inner.value is 1
            writer.set('value', 2);
            // writer.get('value') returns 2
            // outer.value is 1
            // inner.value is 2
          });
        });
      });
    
      setTimeout(function() {
        // runs with the default context, because nested contexts have ended
        console.log(writer.get('value')); // prints 0
      }, 1000);
    }
  3. Install and use cls-hooked

    master

    The cls-hooked package provides Continuation-Local Storage (CLS) by automatically selecting the appropriate underlying mechanism based on your Node.js version:

    • Node.js >= 8.0.0: Uses the native async_hooks API.
    • Node.js < 8.0.0: Uses AsyncWrap and async-hooks-jl (legacy mode).

    To use the library, require the module. It exports the context management API (including createNamespace, getNamespace, and destroyNamespace) via the ./context or ./context-legacy modules depending on your runtime.

    const cls = require('cls-hooked');
    
    // The exported object provides access to Namespace management
    // e.g., cls.createNamespace('my-namespace');
  4. Create a fresh context at invocation time using createContext()

    master

    If you want to use namespace.bind() but ensure the context is captured at the moment the function is called rather than when it is bound, use namespace.createContext().

    function doSomething(p) {
      console.log("%s = %s", p, ns.get(p));
    }
    
    function bindLater(callback) {
      return writer.bind(callback, writer.createContext());
    }
    
    setInterval(function () {
      var bound = bindLater(doSomething);
      bound('test');
    }, 100);
  5. Bind an EventEmitter to a namespace

    master

    To ensure that events emitted by an object (like an HTTP request or response) are correctly scoped within a namespace, use namespace.bindEmitter(emitter). This is particularly useful when using Express or Connect to ensure middleware execution is compatible with CLS.

    http.createServer(function (req, res) {
      writer.bindEmitter(req);
      writer.bindEmitter(res);
    
      // do other stuff, some of which is asynchronous
    });
  6. Manage namespaces with cls methods

    master

    Use the following methods to manage the lifecycle of namespaces in your application:

    • cls.createNamespace(name): Creates a new namespace. Each application should create its own unique namespace.
    • cls.getNamespace(name): Retrieves an existing namespace by its name.
    • cls.destroyNamespace(name): Disposes of an existing namespace. Warning: Ensure you dispose of all references to destroyed namespaces, otherwise contexts associated with them will no longer propagate.
    • cls.reset(): Completely resets all namespaces. Warning: This stops propagation, but if code still holds references to namespaces, the associated storage remains reachable even if the state is no longer updated.
  7. Use the Namespace class API

    master

    The Namespace class is the primary interface for managing scoped values.

    Core Methods

    • namespace.run(callback): Creates a new context and runs the provided callback within it. The context is passed as an argument to the callback.
    • namespace.runAndReturn(callback): Same as run(), but returns the return value of the callback instead of the context.
    • namespace.set(key, value): Sets a value on the current continuation context. Must be called within an active chain started by .run() or .bind().
    • namespace.get(key): Retrieves a value from the current context. It searches recursively from the innermost to the outermost nested context. Returns undefined if not found.
    • namespace.active: Returns the currently active context on the namespace.

    Binding and Contexts

    • namespace.bind(callback, [context]): Binds a function to the namespace (similar to Function.bind). If context is omitted, it defaults to the currently active context or creates a new one.
    • namespace.bindEmitter(emitter): Binds an EventEmitter to the namespace. This is useful for ensuring middleware in frameworks like Express or Connect plays nicely with CLS.
    • namespace.createContext(): Returns a context cloned from the currently active context. Use this with bind() if you want a fresh context at invocation time rather than at binding time.
  8. Check if cls-hooked is active via process.namespaces

    master

    Because cls-hooked has a performance cost, it is not enabled until the module is loaded for the first time. You can check if the module is active and available by testing for the existence of process.namespaces.

    Library code that wants to use continuation-local storage only when it is active should use this check to avoid overhead or errors.