π Hello Everyone..!!!
As a javascript developer, even though you don't implement your own asynchronous functions very often, you are very likely to need to use them in your everyday project.
Typically there are two/three ways to deal with asynchronous functions.
- Callbacks
- Promises
- Async/Await (i.e Promises)
You can read more about these here.
Problem Statement
When you have a chain of asynchronous calls (callbacks or promises), how do you share a common context between all these calls?
Let's think of the following example.
You are writing a function called getCustomerOrders()
that returns customer details along with his/her active orders. Inside that function you have to call asynchronous getCustomer()
and asynchronous getOrders()
where both of these function needs a customerId
from in the Request.
Solution is simple right? π
You just extract the customerId
from the Request and pass it to both getCustomer()
and getOrders()
as function parameters.
const getCustomer = async (customerId: string): Promise<Customer> => {
return fetchCustomerFromApi(customerId);
};
Yes, this is probably the best way that you share context between asynchronous calls. But do you know an alternative way to share context without passing as parameters?
AsyncLocalStorage
AsyncLocalStorage
class of async_hooks
module is released as part of Node.js 14.
As per the NodeJS official documentation
These classes are used to associate state and propagate it throughout callbacks and promise chains. They allow storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.
In simple terms, this acts as a global variable that scoped to a particular asynchronous execution context.
Let's see AsyncLocalStorage in action
Let's see how we can refactor our getCustomerOrders()
example to use AsyncLocalStorage
- First, import
AsyncLocalStorage
fromasync_hooks
module.
import { AsyncLocalStorage } from "async_hooks";
- Next, you have to create instance from
AsyncLocalStorage
representing the data that you are going to share. In this example we are going to store thecustomerId
.
const userAsyncLocalStorage = new AsyncLocalStorage<{ customerId: string }>();
- Now, you have to wrap the
getCustomerOrders()
function usingAsyncLocalStorage.run
function. This is where all the magic happens. As the first parameter to therun
function, you can pass the data that you want to share.
userAsyncLocalStorage.run(
{
// customerId is read from the Request
customerId: "123456789",
},
async () => {
const customer = await getCustomer();
const orders = await getOrders();
// write data to Response
console.log({
customer,
orders,
});
}
);
- Finally, inside the
getCustomer()
andgetOrders()
you can retrievecustomerId
as below.
const getCustomer = async () => {
const { customerId } = userAsyncLocalStorage.getStore();
return fetchCustomerFromApi(customerId);
}
That is the end of very basic application using AsyncLocalStorage
.
Usage
Global state or variables are generally considered bad
as they make testing and debugging a lot harder. Therefor the pattern of using AsyncLocalStorage
to share business data across multiple asynchronous calls (like we share customerId
) is not recommended.
But AsyncLocalStorage
pattern comes in handy when you develop/use APM Tools, which collect performance metrics.
This post explains how you can use AsyncLocalStorage
to create a simple logger component.
Also NodeJS Frameworks like adonisjs uses AsyncLocalStorage
extensively during the HTTP requests and set the HTTP context as the state.
You can read more about that here.
β€οΈ Appreciate your feedback and thank you very much for reading...!!
Top comments (2)
If you're going to use application state, you're defeating the purpose of statelessness.
I feel it would be better to use a class instance that stores the data and the methods would be able to call the state from the instance in this case. This way you can keep "application state" within the context of the execution and not globally.
π Hello Henry, Thank you for reading the post and for the comment. πββοΈ
I totally agree. In my opinion your approach is also boils down to the first approach that mentioned in the post and it is the best way out there.
AsyncLocalStorage
comes handy in certain scenarios and I wanted to provide a sneak-peek into it.