Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to work around promises when using continuation-local-storage?

continuation-local-storage seems to be used, also in context of express.

Yet the very basic usage does not work for me, since the context is completely lost!

var createNamespace = require('continuation-local-storage').createNamespace;
var session = createNamespace('my session');


async function doAsync() {
    console.log('Before getting into the promise: ' + session.get('test'));
    await Promise.resolve();
    console.log('*in* the promise: ' + session.get('test'));
}

session.run(() => {
    session.set('test', 'peekaboo');
    doAsync();
});

results in:

$ node server_test.js 
Before getting into the promise: peekaboo
*in* the promise: undefined

Have I done something completely wrong or is the CLS simply broken? Or is the library broken? If it's not meant to work with promises, are there other concepts that work as a threadLocal storage to implement multi-tenancy in a proper way?

like image 559
estani Avatar asked Jun 25 '19 09:06

estani


2 Answers

cls-hooked seems to be working fine, though the library (as the previous one) were last updated two years ago...

If someone has some other more robust way to implement a thread-local state for multi-tenancy please share!

like image 80
estani Avatar answered Sep 20 '22 11:09

estani


You are trying to maintain a shared state across multiple functions which are then executes asynchronously. That is a very common case in JS, and the language itself provides a very simple, yet powerful mechanism for that: You can access variables from an outer function from an inner function, even if the outer function already finished its execution:

 (function closured() {
    let shared = { /*...*/ };

    function inner() {
      console.log( shared );
    }
    setTimeout(inner);
  })();

Now while that works, it doesn't scale that well for larger applications: All functions accessing that state have to be inside of one function, and therefore that file gets really blown up. The libraries you are using are trying to resolve that, they also use closures to maintain the state across asynchronous calls: When you register an async callback the state is stored in a function, then the callback itself gets wrapped into a callback that restores the state:

   let state;

   function setTimeoutWithState(cb, time) {
      const closured = state;
      setTimeout(() => {
        state = closured;
        cb();
      }, time);
  }

  state = 1;
  setTimeoutWithState(() => console.log(state), 1000);

  state = 2;
  setTimeoutWithState(() => console.log(state), 500);

Now the library just has to wrap every async callback that way, and then you can maintain the state easily, isn't that great? Well for sure it is, but adding code to every callback does have it's cost (as JS heavily utilizes callbacks).

like image 20
Jonas Wilms Avatar answered Sep 18 '22 11:09

Jonas Wilms