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

Publishing an App on F-Droid

󰃭 2021-08-03 | #android #f-droid #tips #tutorials

I made some small apps for Android and I wanted to distribute them. I also care a lot about software freedom, so F-Droid is the best place for me to publish my apps. Disclaimer! You are not able to sell your app on F-Droid. If you want to make money with it, you would need to allow users to pay through another method. Please see this StackExchange question if you’re interested in monetization.

Continue reading 


Learning a Programming Language

󰃭 2014-02-14 | #javascript #programming-projects #ramblings

I’ve heard that if you’re a good programmer, you should be able to pick up a new language in a few days. And I’ve done this myself, I learned the basics of Go a few months ago. Built some command line programs and a web app with it, all in a few days. Yet, what does it take to really learn a new language? To master it? Sure, there are some concepts that translate well from language to language, but some languages have their own thing going on.

Continue reading 