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

Messing Around With Trig

󰃭 2013-03-05 | #canvas #fun #javascript #programming-projects

Yesterday after work I had some time to waste, so I decided to do some silly things in javascript with a canvas and some trigonometric magic. Move your mouse around to change some variables, and click to toggle clearing of the canvas. You can achieve some trippy effects! colorful sine wave colorful flower These are all on my github, in case you wanna take a look.

Continue reading 


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 