Возможно ли иметь локальные переменные "thread" в узле?

Я хотел бы сохранить переменную, которая распределяется между всеми кадрами стека (сверху вниз) в цепочке вызовов. Очень похоже на ThreadLocal на Java или С#.

Я нашел https://github.com/othiym23/node-continuation-local-storage, но он продолжает терять контекст для всех моих случаев использования, и кажется, что вам нужно исправить библиотеки, которые вы используете, чтобы сделать их локальными, что более или менее невозможно для нашей базы кода.

Действительно ли в Узел нет других опций? Могут ли домены, stacktraces или что-то в этом роде использовать для получения дескриптора (id) в текущей цепочке вызовов. Если это возможно, я могу написать собственную локальную реализацию потока.

Ответы

Ответ 1

Да, это возможно. Томас Уотсон рассказал об этом в NodeConf Oslo 2016 в своем Instrumenting Node.js в производстве

Он использует трассировку Node.js - AsyncWrap (которая в конечном итоге должна стать хорошо зарекомендовавшей себя частью общего Node API). Вы можете увидеть пример в агенте Opbeat Node с открытым исходным кодом или, что еще лучше, проверить слайды и примерный код.

Ответ 2

Теперь, когда прошло более года с тех пор, как я изначально задал этот вопрос, наконец, похоже, что у нас есть рабочее решение в виде Async Hooks в Node.js 8.

https://nodejs.org/api/async_hooks.html

API все еще экспериментальный, но даже тогда похоже, что уже есть вилка Continuation-Local-Storage, которая внутренне использует этот новый API.

https://www.npmjs.com/package/cls-hooked

Ответ 3

TLS используется в некоторых местах, где обычные однопоточные программы будут использовать глобальные переменные, но там, где это было бы неуместным в многопоточных случаях.

Поскольку javascript не имеет открытых потоков, глобальная переменная является самым простым ответом на ваш вопрос, но использование одного из них - плохая практика.

Вместо этого вы должны использовать закрытие: просто оберните все ваши асинхронные вызовы в функцию и определите там свою переменную.

Функции и обратные вызовы, созданные при закрытии

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

Функции, созданные вне закрытия

Если вам требуется, чтобы ваша переменная была видимой внутри функций, не определенных в области запроса, вы можете вместо этого создать объект контекста и передать его функциям:

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

Используя подход выше всех ваших функций, называемых внутри c() обращайтесь к одному и тому же объекту ctx, но разные вызовы c() имеют свои собственные контексты. В типичном случае использования c() будет вашим обработчиком запросов.

Связывание контекста с this

Вы можете связать свой объект контекста с this в вызываемых функциях, вызвав их через Function.prototype.call:

functionWithCallback.call(ctx, ...)

... создание нового экземпляра функции с Function.prototype.bind:

var boundFunctionWithCallback = functionWithCallback.bind(ctx)

... или используя функцию полезности обещания, такую как bluebird .bind

Promise.bind(ctx, functionReturningPromise(data) ).then( ... )

Любой из них сделает ctx доступным внутри вашей функции следующим this:

this.visibleToAll ++;

... однако это не имеет реальное преимущество над проходя контекст вокруг - ваша функция по- прежнему должен быть в курсе контекста прошло через this, и вы можете случайно загрязняет глобальный объект, если вы когда - либо вызова функции без контекста.