629

June 19th, 2023 × #javascript#nodejs#webdev

AsyncLocalStorage + AsyncContext API

Discussion on AsyncLocalStorage and Async Context API in JavaScript which allow accessing context down the call stack without passing references explicitly.

or
Topic 0 00:00

Transcript

Wes Bos

Boss, and Scott

Scott Tolinski

Welcome to Syntex.

Scott Tolinski

On this Monday, HD Treat, we're gonna be talking about sync local storage in the async context API in JavaScript. This is a server side JavaScript thing that allows you to basically have a context amongst your entire call stack for any given piece of information that you want to share in that regard.

Scott Tolinski

So my name is Scott Cholinski. I'm a developer from Denver. And with me as always is Wes

Wes Bos

balls. Hey, excited to talk about it.

Wes Bos

I have done a I did a little TikTok on it a couple of weeks ago, and then I talked about it in my Reactathon, talk, which was on, like, next gen JavaScript frameworks.

Wes Bos

A cyclical surge is pretty cool. So we're gonna kind of run through what it is and and why you might need it.

Scott Tolinski

Should we get on into it? We should. And I I will be taking the role of somebody who has not used it. I generally know what it is, but I will be trying to ask you some questions. So if if my question seem dumb Or if they seem, like, off based, try to steer me in that direction because I'm interested in this stuff. And I I kinda have an idea of when you would use it, but I I wanna Really get the full picture here. So, async local storage.

Scott Tolinski

My understanding is that this is a new API within JavaScript Inside of Node specifically as well as what these these, other runtimes, other JavaScript runtimes. Yeah. When did this drop in Node? And Is it like are all these JavaScript run times aligned on this?

Topic 1 02:05

AsyncLocalStorage added in Node 13

Wes Bos

So this dropped in Node 13. So meaning it's been around for 3 years already in Node.

Wes Bos

However, it has been gaining a little bit of momentum recently because of a few reasons.

Topic 2 02:13

Gaining momentum due to Async Context API proposal

Wes Bos

One of the reasons is that this is so this is a Node API.

Wes Bos

However, there is now a proposal in TC39 for something called the Async Context API, which does pretty much the exact same thing, which means that people saw this in notes, Hey, that's actually kind of helpful.

Wes Bos

Wouldn't it be cool if that was just a feature we had in JavaScript and everywhere? So let's explain. In client side JavaScript. Yeah, Yeah, exactly. Like, I have a couple of examples of, like, why would you ever want this in the browser as well? So I think local storage. It's kind of like a bad name because it doesn't have anything to do with local storage.

Topic 3 02:38

Allows accessing context down call stack without passing refs

Wes Bos

It doesn't have an API like local storage, really. It doesn't it's not like it's not like, oh, it's like it's like local storage, but you can async away to it. It's nothing like that.

Wes Bos

It will allow you to access context down the call stack. So before we get into any of these things, let's understand what context is. So when you have a function in JavaScript and you call that function. So like, let's say somebody's going to westboss.com.

Topic 4 03:26

Example of passing context through nested functions

Wes Bos

I'll have a route handler. That's a function, right? And that function will get a request and then we can send a response back from it. Right. And then in that handler, I might have a function that calls another function, right? And that function will say, Okay, let's fetch some values from the database, but that function might call another function. That function might call another function. Right.

Wes Bos

So any a function that is called within my route handler all the way down. You know, a function calls a function that calls another function. If you ever want to share data between all of those function calls.

Topic 5 04:06

Current way is passing references down call stack

Wes Bos

Right now, the way to do it is you just pass some sort of reference or you pass something along from function to function so that anybody that needs it anywhere down the line will be able to have access to it. Sound familiar? We have this problem.

Wes Bos

Where where have you seen this problem before?

Scott Tolinski

Middleware. So, you have like In in Svelte kits, for instance, a request comes in Yeah. Or I wasn't gonna say React content. This is something different. This is, were you thinking React contacts? Is that what you were implying? Both those. I think you should go into both of them. So in middleware, typically, a request comes in maybe like an auth token or something. You validate that user, and then you pass that user information along. In SvelteKit, they do it through something called locals where in all of your request functions, locals is just available, and everything that gets saved to locals is available. But it is passed through the function.

Topic 6 05:00

Examples in SvelteKit and React

Scott Tolinski

The other one might just be in general React context where, Let's say you wanted to essentially avoid that problem of having to pass props from child to child to child to child. You put something in the React context and are able to access it outside of the, the the prop flow of things inside of React. Right? Exactly.

Wes Bos

We have this problem in React and many other, prop based languages where I want something to go from 1 level down 8 levels deep, but I don't want to have to manually pass it along every single way, and we'll talk about the benefits and cons against that in just a sec as well. So async local storage Will allow you to define a store inside of a function and then any function that is called anywhere down The the call stack or any function that is then subsequently called within that function call will be able to essentially just reach into the thin air and access that store and bring it down for that. That makes a lot of sense because you don't have to pass it along to every single function, and it's really nice. So what we're doing right now in a lot of use cases is we have a request object.

Wes Bos

And if you want something to be able to be accessed down the line, a middleware is a really good example where You might have a middleware that, like, populates your user.

Topic 7 06:30

Use case: populating user from database in middleware

Wes Bos

And so the request comes in.

Wes Bos

You populate the user, you get them from the database, and then you stick the whole user on the request object and you call next or you return it. And then the function that gets called next down the line will we'll have access to that because it's on the request and kind of the request object or a lot of frameworks call it a context object.

Wes Bos

It's kind of just a spot where you stick anything you want. Anybody can stick anything on there, and it's really hard to type it. It's really hard to know what's available to yourself.

Topic 8 07:02

Alternative is passing context object with issues

Wes Bos

So the and every single function that does want to be able to access the context Has to then be past it, right? You might have context that like a top level, but what happens if you want it 7 levels deep in your data fetching library, right? You don't necessarily have access to So the async local storage will allow you to define a store and access it anywhere down. It's sort of like I kind of look at it as a closure variable. In JavaScript. If you have declare a variable inside of a function anywhere down the level, you'll be able to access a closure variable, but it doesn't work as soon as you find a function somewhere else.

Scott Tolinski

Are you raising your hand? I'm raising my hand. Oh, what's up? I would like I would just have a quick question. I wanna I just wanna interrupt. I wanted to get this before you moved on from this.

Scott Tolinski

Why would you say that most frameworks today in node aren't using async local storage for context,

Wes Bos

for middleware. Yeah. That's a great question. I think because it is a relatively new API And a lot of frameworks these days, I specifically have been using one called Hano Jet. Js, A lot of these frameworks are trying to work on every single deploy target ever. Oh, I see. So using Only using a node API is kind of a no go for a lot of them. I went into I was like, Okay, is this in Deno? And I literally found the thread where Ryan Dahl goes, This is cool. I can see where this would be helpful, but I don't want to like, why are we using just a node API? So that's why they're not like a stand alone.

Topic 9 08:46

Not in other runtimes yet, needs JS proposal

Wes Bos

So obviously they polyfilled it and you can use it in Deno. But there is a proper,

Scott Tolinski

TC 30 9 proposal to to add this to the language. So you don't you don't see this as being a part of a lot of things until the actual JavaScript

Wes Bos

proposal gets pushed through. That's a good question. But, so Next. Js has implemented it. Cloudflare Workers has implemented it, and Next. Js uses it to be able to access cookies and headers within Next. Js. So if you have an action or a server function or anything in Next. Js And you want to be able to access the headers or the cookies, you simply just import a function from Next. Js library called cookies. You run cookies and that will give you the cookies function internally will reach out into the sky, Grab the async local storage value down and return to you all the cookies. That's super nice because, again, you don't have to pass the headers and the cookies every single function that needs it. You can simply just access it wherever it is that you need it. So I think we are starting to see This type of thing being implemented. Again, it's only 2 or 3 years old. I guess that is kind of long, but we're starting to see a lot of people Find this useful because you can also, I'm assuming with Next. Js, if you have multiple middlewares, you could pass data from 1 middleware to another because there's an API for starting it with existing data.

Wes Bos

So let's go into some actual examples of like, why would you ever want to use this as well as like, isn't this a bad idea For some use cases, probably the most common use case is for logging and tracing of request IDs. Every single time you have a request come in, You can make a unique identifier for that request and use it to access it anywhere down the line. So you could put a cookie in there. We've said you could use logging, tracing.

Topic 10 10:46

Use cases: request IDs, user prefs, logging

Wes Bos

You could query user preferences and just be able to get your users preferences wherever it is that you want.

Wes Bos

It's pretty nice for that type of stuff. I specifically when I was running all the Syntax episodes through the OpenAI API.

Wes Bos

I was firing off 5 at a time and I have like we have a function to condense it. We have a function to summarize it. We have a function to add the speaker, Right? Like, there's probably 20 different functions that are running, and I want a console log running x, y, and z for show number one zero seven for show number 203. Right. But I don't want to pass the show number into every single one of those functions just to console log it. Right. I'm not using the show number anywhere else.

Wes Bos

So why do I have to pass it in just for my own logging needs? Well, when you start off that initial request, We put the show number in the synclical storage and then anywhere else I work to console. Log, I can simply just access it. In fact, I made a custom console log function, and the console log function won't reach into a link, async local storage, grab the show number, and then prepend the show number with the console log. So my console logs are not a huge mess.

Topic 11 11:39

Example of logging show numbers without passing refs

Wes Bos

They're just nicely prefixed with the show number. And if there's an error, I can say, okay, well, I know that this happened on this request Or on this specific show parsing, you might be saying, well, can't you just stick a variable in memory? You know, just make a variable outside your function and reference that on every single time. The problem with that is if you're running 2 functions at once, you're on the first one, you have a request ID and then the 2nd time You have like a that might overwrite each other, because if things happen out of order, then you get into real trouble because those variables in memory are often shared and they're overriding each other. This will simply just make a store When the function starts and then when it's done, the whole thing is garbage collected and you don't have to worry about it anymore.

Wes Bos

The API for it, you can stick anything you want in it. So it's not like local storage where it's a key value. It could be a variable, could be an object. A lot of times people use sets and maps because sets and maps have nice APIs to work with them. You can do whatever you want. You can stick timer IDs in there. Like, Let's say you start a timer in 1 function and then you want to be able to clear the timer in another function.

Topic 12 12:59

Can store anything - objects, maps, sets, timers

Wes Bos

Some. I guess sometimes it makes sense. Just pass that ID around. But if you want to be able to clear the timer, you could just reach into a sync local storage, get the timer ID, Go ahead and clear it.

Wes Bos

Promise chaining each. If you use .then and .catch syntax instead of asyncawait, Sometimes it's annoying to Yeah. You have to if you want like data shared between the 2, you have to like pass it. You have to return it from the then and then destructure it in the next then or you have to like, make a variable outside that's empty and then reach outside and update it right. So With a sync local storage, you could just stick each piece of data that you get into a sync local storage and then when it's all done, You could grab it on on out from that. So those are some kind of use cases. I think in most cases, user sessions, user login and tracing console logs. That's where 98% of this type of thing is going to lie, where you need to be able to access it.

Topic 13 13:33

Useful for promise chaining instead of passing data

Wes Bos

Some foot guns. Having things globally available is makes it kind of hard to test, right? Think about if you want to test a React component, But that React component assumes that it has access to data that is in context.

Topic 14 14:22

Downside is hard to test code relying on external data

Wes Bos

You're going to have the same issues here where your test runner will have to mock that context so that any functions inside of it that reach out will be able to access it. So It's not a very pure function when your function needs to reach into the ether and access the sync local search, right? You can see how that could be a bit of an issue. Yeah.

Wes Bos

And then it could cause a memory leak as well. Just like anything in JavaScript, if you are referencing something and you no longer reference it, JavaScript will garbage collect it. However, sometimes if you reference something in JavaScript that is like a variable somewhere else you would think, oh, yeah, JavaScript will garbage collect it. You can run into an issue where It's not being garbage collected. And every single time you make an object of people, that increases memory usage. And eventually, you're going to run out of memory and your app will fall over. So specifically, this API doesn't cause memory leaks, but it's just another thing to think about if you are referencing an object or value by its name, you got to think about, okay, well, Maybe there's a potential memory leak there. There is an exit method you can do that will explicitly clean it up if you do have that issue, though.

Topic 15 15:00

Can also cause memory leaks if not cleaned up properly

Wes Bos

That is it.

Topic 16 15:55

Useful for browser events instead of passing data around

Wes Bos

It's not a huge use case, but certainly will be handy. I could see this being handy for events in the browser. So, like, if you have An event that fires, instead of passing the data of that event all the way down, You could just stick it in sync context and be able to access that

Scott Tolinski

somewhere else. It feels like I want to do that in my application and stuff using the the context. I don't need to rewrite things, but, like, it seems like that's, like, what I would want to do. Just throw it in a async local storage within my my server side hook.

Scott Tolinski

Yeah. And then

Wes Bos

be able to access it application wide. Sounds pretty neat. Exactly. Like it's similar to like a request object, right? Request comes in. You want to be able to access that data, then maybe an event.

Topic 17 16:43

Expect best practices to emerge over next year

Wes Bos

I foresee us Seeing like best practices come out of this in the next year or so of like people, Oh, we used a sync local storage everywhere and the thing blew up. Or here's a couple of use cases. We did use sync local storage, and it worked out well for us. Very fascinating. Well, cool stuff, Wes. This is the type of stuff that I, personally, you know, wouldn't come across it if it wasn't

Scott Tolinski

or somebody like you, sharing this. Awesome. Alright. Well,

Wes Bos

glad to share, and we'll catch you later. Peace. Peace.

Scott Tolinski

Head on over to syntax.fm for a full archive of all of our shows.

Scott Tolinski

And don't forget to subscribe in your podcast player Or drop a review if you like this show.

Share

Play / pause the audio
Minimize / expand the player
Mute / unmute the audio
Seek backward 30 seconds
Seek forward 30 seconds
Increase playback rate
Decrease playback rate
Show / hide this window