Joppe
Joppe

Reputation: 1524

Is it possible to have "thread" local variables in Node?

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

Answers (4)

Joppe
Joppe

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

Michael Bushe
Michael Bushe

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

Jakub Holý
Jakub Holý

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

Koder
Koder

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.

Functions and callbacks created within closure

  (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++;
          // ...
       });
    ))();

Functions created outside of closure

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.

Binding context to 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

Related Questions