Context Dependent Imports Using CLS and Getters
As I was reading this article about exporting getters in CommonJS from my colleague David Barral the other day, it occurred to me that we could use this approach to export dynamically from a CLS context. Since I recently wrote a post about AsyncLocalStorage, I thought that I could probably dig a bit deeper on this subject.
Refactoring our CLS example
In my previous AsyncLocalStorage post I used this example:
We can make it way more readable by taking out all the CLS stuff and moving it to a separate file. This is the content of context.js
:
We added a couple of lines here. We defined a default store and we assigned it to the AsyncLocalStorage
with the enterWith
method. This store will be returned when we’re out of the request scope.
This is our main.js
using the new module:
Using the getter magic
The cool thing about getter exports being dynamic is we can encapsulate the logic of taking the requestId
out of our context in the context.js
module.
We add the requestId
getter to the module.exports
, and the result looks like this:
Since now we can access requestId
just like any other attribute from that module, we get this nice API:
If we access ctx.requestId
out of scope we get a not-in-a-request
id, otherwise we get the proper request identifier. We can expand the context.js
API as much as we want by adding more getters to the module.exports
.
Careful, this is magic
Exporting getters is a good way to make clean APIs but if we are not aware that we are using them bad things can happen. Let’s check this other scenario:
If we make an HTTP request to the service now we get this:
[not-in-a-request] request received
What’s happening here?
We are destructuring the module outside of scope. Since requestId
is a getter and we are using the destructuring assignment at the top level, we are assigning its value outside of the request scope. This causes not-in-a-request
to be assigned to the requestId
variable.
Should you do this?
This is the same question David Barral asked at the end of his post. To be honest, I wasn’t a big fan of exporting getters the first time I read about it. For this specific use case though, I think it makes things more readable and clear.
You can find the example code in this repository.
Enjoy!