Using AsyncLocalStorage for Better Traceability in NodeJS Applications

󰃭 2024-09-29

NodeJS has a neat API called AsyncLocalStorage. It’s used to share information across callbacks and promise chains.

For example, it is useful to share information for all the code executed when serving a web request. I also found it very useful to keep trace information to easily keep track of which item was being processed during a batch process.

The only caveat in my opinion, is that you need to wrap your code in yet another callback.

How to use AsyncLocalStorage:

  1. First, create an instance of AsyncLocalStorage:

    const asyncLocalStorage = new AsyncLocalStorage();
    
  2. Then, use the run method to wrap your code:

    const sharedState = { hello: "world" };
    await asyncLocalStorage.run(sharedState, async () => {
      await foo();
    });
    
  3. Finally, use the getStore method to retrieve the shared state:

    async function foo() {
      const sharedState = asyncLocalStorage.getStore();
      console.log(sharedState);
    }
    

    The output will be:

    { hello: "world" }
    

Real-world example

The previous example is very simple because it only shows how to use it.

Here’s a more elaborate example that will hopefully show why this feature is so useful:

import { AsyncLocalStorage } = from "node:async_hooks";

async function process() {
  try {
    await Promise.all([foo(), bar()]);
  } catch (err) {
    handleError(err);
  }
}

async function foo() {
  const { index } = asyncLocalStorage.getStore();
  console.log(`processing foo ${index}`);
}

async function bar() {
  const { index } = asyncLocalStorage.getStore();
  console.log(`processing bar ${index}`);
  
  if (Math.random() < 0.1) {
    // simulate a random failure
    throw new Error("A random failure happened");
  }
}

async function handleError(err) {
  const { index } = asyncLocalStorage.getStore();
  console.log(`Handling error when processing ${index}: ${err}`);
}

const asyncLocalStorage = new AsyncLocalStorage();

// process 20 things, asynchronously
const tasks = [];
for (let i = 0; i < 20; i++) {
  tasks.push(
    asyncLocalStorage.run({ index: i }, process)
  );
}

If you execute this code, it will clearly log for which items the failures occured, because the required information is available in the shared state exposed by AsyncLocalStorage.

Example of the output:

processing foo 0
processing bar 0
processing foo 1
processing bar 1
...
Handling error when processing 3: Error: A random failure happened
Handling error when processing 8: Error: A random failure happened
Handling error when processing 14: Error: A random failure happened


More posts like this

Setting up sendmail to redirect emails

󰃭 2016-05-14 | #email #linux #tutorials

Disclaimer: the instructions below are for Ubuntu, but they should work for most distros, the biggest difference is that the configuration files might be located elsewhere. If you’re like me, you have a main email address and other email addresses set up in other domains. I dislike having to check all my email addresses individually, so I set up my mail servers to redirect all the email to my main address automatically.

Continue reading 


Starfield visualization in JavaScript

󰃭 2024-08-31 | #canvas #javascript #programming-projects #tutorials

This is a simple, straightforward implementation of a visualization reminiscent of the classic Windows 95 starfield screensaver. It is also interactive: you can touch the screen or use the accelerometer to influence the direction of the movement. This is how it works: Create a bunch of particles (100), each in a random position. Every frame, move each particle further away from the center*. The further the particle is from the center, the more visible it will become.

Continue reading 