Reputation: 1524
I would like to store a variable that is shared between all stack frames (top down) in a call chain. Much like ThreadLocal in Java or C#.
I have found https://github.com/othiym23/node-continuation-local-storage but it keeps loosing context for all my use cases and it seems that you have to patch the libraries you are using to make it local-storage-aware which is more or less impossible for our code base.
Are there really not any other options available in Node? Could domains, stacktraces or something like that be used to get a handle (id) to the current call chain. If this is possible I can write my own thread-local implementation.
Upvotes: 27
Views: 16083
Reputation: 1524
Now that more than a year has passed since I originally asked this question, it finally looks like we have a working solution in the form of Async Hooks in Node.js 8.
https://nodejs.org/api/async_hooks.html
The API is still experimental, but even then it looks like there is already a fork of Continuation-Local-Storage that uses this new API internally.
https://www.npmjs.com/package/cls-hooked
Update According to the comment below, AsyncLocalStorage now seems to be the supported way to do this: https://nodejs.org/api/async_context.html#class-asynclocalstorage
Upvotes: 9
Reputation: 1561
Node.js 20 supplies AsyncLocalStorage for context tracing. It looks like even the experimental Hooks was not a good solution. If you need context for something other than tracing you should open an issue.
Upvotes: 4
Reputation: 6195
Yes, it is possible. Thomas Watson has spoken about it at NodeConf Oslo 2016 in his Instrumenting Node.js in Production (alt.link).
It uses Node.js tracing - AsyncWrap (which should eventually become a well-established part of the public Node API). You can see an example in the open-source Opbeat Node agent or, perhaps even better, check out the talk slides and example code.
Upvotes: 18
Reputation: 474
TLS is used in some places where ordinary, single-threaded programs would use global variables but where this would be inappropriate in multithreaded cases.
Since javascript does not have exposed threads, global variable is the simplest answer to your question, but using one is a bad practice.
You should instead use a closure: just wrap all your asynchronous calls into a function and define your variable there.
(function() (
var visibleToAll=0;
functionWithCallback( params, function(err,result) {
visibleToAll++;
// ...
anotherFunctionWithCallback( params, function(err,result) {
visibleToAll++
// ...
});
});
functionReturningPromise(params).then(function(result) {
visibleToAll++;
// ...
}).then(function(result) {
visibleToAll++;
// ...
});
))();
Should you require your variable to be visible inside functions not defined within request scope, you can create a context object instead and pass it to functions:
(function c() (
var ctx = { visibleToAll: 0 };
functionWithCallback( params, ctx, function(err,result) {
ctx.visibleToAll++;
// ...
anotherFunctionWithCallback( params, ctx, function(err,result) {
ctx.visibleToAll++
// ...
});
});
functionReturningPromise(params,ctx).then(function(result) {
ctx.visibleToAll++;
// ...
}).then(function(result) {
ctx.visibleToAll++;
// ...
});
))();
Using approach above all of your functions called inside c()
get reference to same ctx
object, but different calls to c()
have their own contexts. In typical use case, c()
would be your request handler.
this
You could bind your context object to this
in called functions by invoking them via Function.prototype.call
:
functionWithCallback.call(ctx, ...)
...creating new function instance with Function.prototype.bind
:
var boundFunctionWithCallback = functionWithCallback.bind(ctx)
...or using promise utility function like bluebird's .bind
Promise.bind(ctx, functionReturningPromise(data) ).then( ... )
Any of these would make ctx available inside your function as this
:
this.visibleToAll ++;
...however it has no real advantage over passing context around - your function still has to be aware of context passed via this
, and you could accidentally pollute global object should you ever call function without context.
Upvotes: 1