---
title: "Node.js 14 & AsyncLocalStorage: Share a context between asynchronous calls"
description: "How to share a context between asynchronous calls with AsyncLocalStorage.\nConcrete use case with an application logger and future use cases presentation."
---

[Kuzzle blog](https://blog.kuzzle.io)

# [Node.js 14 & AsyncLocalStorage: Share a context between asynchronous calls](https://blog.kuzzle.io/nodejs-14-asynclocalstorage-asynchronous-calls)

 Written by [Alicia Thermos](https://blog.kuzzle.io/author/alicia-thermos) | April 30, 2020

 

Node.js 14 [has been](https://nodejs.org/en/about/releases/) available [since April 21, 2020](https://nodejs.org/en/about/releases/).

This version embeds a new version of v8 but also a lot of new features like the [AsyncLocalStorage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage) class of the [async\_hooks](https://nodejs.org/api/async_hooks.html) module.

This class allows to store and access data throughout a series of asynchronous calls.

 

We will look together at how this can make our lives easier.

 

**TL;DR**

- [AsyncLocalStorage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage) is used to **share data between asynchronous calls**.
- This can be considered as a **global variable** related to a particular **asynchronous execution context.**
- There are many uses, especially for **application analysis tools**.
- **Performance** seems to be **slightly impacted**. ([~8% on Kuzzle](https://github.com/kuzzleio/kuzzle/pull/1604))

## Once upon a time there was a logger in an asynchronous web application

Let's imagine a web application that receives a request.

 

The entry point of my application is the function receiving a request and calling a service

 

```
const app = express();
 
const log = message => console.log(message);
 
const emailService = new EmailService(logger);
 
app.get('/', (request, response) => {
  const requestId = uuid();
  log(`[${requestId}] Start processing`);
 
  await emailService.notify(request.body.emails, requestId);
  response.writeHead(200);
});

```

 

In my application, I want to be able to **add the unique identifier of the request to each log line** so that I can correctly trace all related actions.

 

So if I want to log something in the *EmailService*, I'll have to give it at least the request ID.

 

```
class EmailService {
  constructor (log) {
    this.log = log;
  }
 
  async notify (emails, requestId) {
    for (const email of emails) {
      this.log(`[${requestId}] Send email: ${email}`);
 
      sendGrid.send(email);
    }
  }
}
```

 

The problem is that my *EmailService* **is only responsible for sending emails**.

So it should not receive a *requestId*, because it shouldn’t care about a request!

 

Also, if you develop and [**distribute a backend**](https://github.com/kuzzleio/kuzzle) or framework to other users, **you can't force them to add a parameter to all their methods** to pass the unique identifier of the request in case they want to log something.

 

So we'll have to find a way to retrieve the right *requestId* each time we want to log something.

## How about we share some context in our asynchronous journey?

[AsyncLocalStorage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage) provides a simple API to **share a context in a succession of asynchronous calls**.

 

Regardless of the number of services and methods used, we will always be able to retrieve our context and use it to store data.

 

First of all, we will wrap all our treatments in the callback of the [AsyncLocalStorage.run](https://nodejs.org/api/async_hooks.html#async_hooks_asynclocalstorage_run_store_callback_args) method.

 

The first argument of this method will be attached to the asynchronous context started with the callback passed as the second argument.

 

```
const { AsyncLocalStorage } = require('async_hooks');

const asyncLocalStorage = new AsyncLocalStorage();

app.get('/', (request, response) => {
  const requestId = uuid();
 
  asyncLocalStorage.run(requestId, async () => {    
    // entering asynchronous context
    log('Start processing')
    await emailService.notify(request.body.emails);
    response.writeHead(200);
  });
});
```

 

 

Next, we will modify the log method in order to retrieve the *requestId* from the current asynchronous execution context.

 

For this, we will use the [AsyncLocalStorage.getStore](https://nodejs.org/api/async_hooks.html#async_hooks_asynclocalstorage_getstore) method.

 

```
const log = message => {
  const requestId = asyncLocalStorage.getStore();
  
  if (requestId) {
    console.log(`[${requestId}] ${message}`);
  }
  else { 
    console.log(message);
  }
};
```

 

 

So finally in our *EmailService* we don't need to pass the *requestId* anymore, we just use the log method which will **retrieve the *requestId* from the current asynchronous context**.

## Benchmark and overhead in real situation

Of course there is an overhead to using [AsyncLocalStorage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage) and [async\_hooks](https://nodejs.org/api/async_hooks.html) in general.

 

At Kuzzle we always pay attention to the performance of our backend, that's why we wanted to **know what the cost of this new feature** was.

 

*A* [micro benchmark](https://gist.github.com/Aschen/5cc1f3f3b58f1e284b670b83bb53da7d)*was also carried out but as always this kind of results is to be taken with caution and a test in real condition should always be carried out before a decision is taken.*

 

So we added *AsyncLocalStorage* to the execution of a request in order to keep the request ID so that we can add it to each log line. ([Code available on Github](https://github.com/kuzzleio/kuzzle/pull/1604))

 

Then we tested the performance with [WRK](https://github.com/wg/wrk), a tool to make HTTP benchmarks. These performance tests were done with *NODE\_ENV=production*.

 

|   | **  Log w/ AsyncLocalStorage** | **  Log classic** | **  Difference** |
| --- | --- | --- | --- |
| **  Requests/sec** |   *2613* | *  2842* | *  ~ 8%* |

 

There is a drop in performance of less than 10%, which is still acceptable given the possibilities offered by this new API.

## Much better than an improved logger

*AsyncLocalStorage* opens **many perspectives in terms of operational analysis** of your application, indeed it is possible to **link all the events triggered by the same request.**

 

In our example, our shared context is simply a *String* but it is quite possible to share more complex objects such as a *Map* for example.

 

```
const contextStore = new Map();
contextStore.set('requestId', uuid());
 
asyncLocalStorage.run(contextStore, async () => {
 // entering asynchronous context
});
```

 

This opens many possibilities and use cases.

### Trace queries made to the database

We could **know the exact number of queries to Elasticsearch triggered by a request** with other information such as the amount of data transferred and the execution time.

 

This would provide a very good **tool for analyzing the performance of an application**.

### Know the complete list of API methods used by a query

In Kuzzle, a request to the API can potentially trigger other actions with hooks/pipes or in a method of a plugin controller.

 

It would therefore also be possible to **list all the methods used during request execution**. (Eg: 3 *document:creat*e, 4 *document:get*, *2 collection:exists*)

 

For example, we could create reports to **analyze the underlying complexity of an API method**.

### Better management of callback errors

Most of the time when working with callbacks, **the first argument is an error**.

If the execution has a problem somewhere, you have to go up the whole callback chain by passing this error.

 

One could imagine a system where the **callback of the query response is stored** in *AsyncLocalStorage* and all other callbacks would take only one argument.

 

An exception would be used to raise an error and a **global error handler would retrieve the response callback associated with the execution context** to call it with the caught error.

## When is it due in Kuzzle?

We plan to wait for the **next release of Node.js 12 **([12.17.0](https://github.com/nodejs/node/pull/33197)) before starting to use *AsyncLocalStorage* because this feature should be backported in this version of Node.js.

 

In the meantime do not hesitate to test it on your own applications!

 

 

I would like to thank my colleagues from Kuzzle for proofreading this article as well as [Vladimir de Turckheim](https://twitter.com/poledesfetes), the author of AsyncLocalStorage API.

 

If you have any questions or remarks, don't hesitate to come and talk to us on [Kuzzle Discord](http://join.discord.kuzzle.io) ;-)

[View full post](https://blog.kuzzle.io/nodejs-14-asynclocalstorage-asynchronous-calls)

```json
{
  "@context" : "http://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Alicia Thermos"
  },
  "dateModified" : "2025-07-21T13:47:43.340Z",
  "datePublished" : "2020-04-30T07:24:14Z",
  "headline" : "Node.js 14 & AsyncLocalStorage: Share a context between asynchronous calls",
  "image" : {
    "@type" : "ImageObject",
    "height" : 284,
    "url" : "https://cdn2.hubspot.net/hubfs/3803374/blog%20post%20hubspot_%20%20Node.js%2014%20AsyncLocalStorage.png",
    "width" : 768
  },
  "mainEntityOfPage" : "https://blog.kuzzle.io/nodejs-14-asynclocalstorage-asynchronous-calls",
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "height" : 60,
      "url" : "/hs/hsstatic/content_shared_assets/static-1.4092/img/default-amp-logo.png",
      "width" : 60
    },
    "name" : "Kuzzle blog"
  }
}
```