継続ローカルストレージ変数を使用してノードコードを簡素化



TLドクター
  • ノードのコードを通してリクエストのローカルコンテキスト変数を持つ簡単な方法があります.
  • このテクニックを使えばcls.anythingYouLike = somethingElse そして、それは現在の要求によって呼ばれるコードのどこにでもセットされて、見つけられます、しかし、他の要求と干渉しません.
  • 大幅に整理し、変数の上下のサブルーチン間の必要性を削除することによって混乱と混乱を減らす.
  • 大きな特徴はCLSを役に立つ機能で飾ることができることですaudit それは誰が現在のユーザーが知っているし、コンテキストの多くを渡す必要がなく、どこでもそれらを呼び出すことができます.
  •    function someDeepRoutine(param) {
          // Audit that the current user has accessed this function
          // Without us having to explicitly pass lots of identity
          // variables...
          cls.audit("deepRoutineExecuted", {param})
       }
    
  • 私はあなた自身のコードで使用できるMITライセンスライブラリとしてそれを実装してGitHub or npm -i simple-continuation-local-storage .
  • 以下にどのように動作するかを説明します.

  • 考え
    我々はフロントエンド上のアプリケーションの状態を管理する方法のすべての種類がありますが、サーバーになると私たちは失われた自分自身を見つけることができるパラメータやコンテキスト変数の質量は、何かの場合に何かを介して転送する必要があります後に何かが必要です.
    これは、さまざまなユーザーに対して並列処理をしているものに対して、グローバルな状態を持つことができないからです.最高の状態でコンテキストを作成し、それを関連付けることができますが、継続的なローカルストレージを使用して簡単に方法があります.
    CLSは、スレッドローカルのストレージのようなビットで、特にスレッドによって所有されているので、名前が付けられます.現在の実行コンテキストのスコープです.だから、どのように多くの継続がサーバーを介して流れている、関係なく、それぞれの独自のコピーを持っている.
    今のところ、これの実装の数があったが、私はそれらをすべて複雑すぎる(名前空間などを取得)を使用し、いくつかのコードを続けている-私は何かを“グローバル”の変数のような感じが欲しいが私のために管理されます.
    私のサーバはすべて今すぐに実行されますが、私たちによって引き起こされる小さなオーバーヘッドがありますがasync_hooks あなたが“継続”を作成するたびに呼び出されます-あなたはすぐにコードがかなりタイトで表示されます.

    CLSライブラリの使用
    CLSを使用するには、それをインストールし、それを必要とするだけで、リクエスト応答をラップするために$ initメソッドを使用します.その後のようにglobal でも知ってる.local !
    const events = require('event-bus');
    const cls = require('simple-continuation-local-storage')
    
    app.get('/somepath', cls.$init(async function(req,res) {
       cls.jobs = 0;
       cls.req = req;
       cls.anything = 1;
       await someOtherFunction();
       res.status(200).send(await doSomeWork());
    })
    
    async someOtherFunction() {
      await events.raiseAsync('validate-user');
    }
    
    events.on('validate-user', async function() {
       const token = cls.req.query.token;
       cls.authenticated = await validateToken(token);
    });
    
    async validateToken(token) {
       await new Promise(resolve=>setTimeout(resolve, 100));
       return true;
    }
    
    async doSomeWork() {
        cls.jobs++;
        await new Promise(resolve=>setTimeout(resolve, 1000));
        return [{work: "was very hard"}];
    }
    
    あなたが見ることができるように、それはちょうどあなたがグローバルを使用していたようです.何か-しかし、それはすべての要求に一意になるだろう.

    動作方法
    使用するCLSasync_hooks ノードの機能は、新しいasyncコンテキストが行われるたびに通知することができます.また、プロキシを使用して、我々は自然と感じるように期待して動作する甘いシンプルなインターフェイスを持つことができます.
    const hooks = require( 'async_hooks' )
    
    const cls = {}
    let current = null
    const HOLD = "$HOLD"
    
    hooks
        .createHook( {
            init ( asyncId, type, triggerId ) {
                let existing = cls[ triggerId ] || {}
                cls[ asyncId ] = existing[HOLD] ? existing : { ...existing, _parent: existing}
            },
            before ( id ) {
                current = cls[ id ] = cls[id] || {}
            },
            after () {
                current = null
            },
            destroy ( id ) {
                delete cls[ id ]
            },
        } )
        .enable()
    
    フックは4つのコールバックを持ちます.init 新しいコンテキストが作成されたときに呼び出されます.これは、asyncコールを行うたびに、毎回返します
    インinit 現在の状態を表す現在のPOJOを取得します.それから、$ hold = trueメンバーがあるならば、我々はちょうどそれを子供に沿って送ります.それがそうしないならば、我々はそれの浅いコピーをして、それを送ります.
    このサーバーのすべては、このフックを通して実行しています-私たちは、ただ一つの要求または他のエントリポイントのメンバーを通して後方に、そして、前方に内容を本当に共有し始めるだけです.言い換えると、要求が終わるまで、どんな関数でもいつでも見つけることができる値を設定することができます.あれcls.$init(fn) 上記の関数で設定します.
    反対のinit is destroy - この時点で我々は再び見られることはありません我々のコンテキストをスローすることができます.before コンテキストが入力される前に呼び出されます-コードが実行される直前にinit . after ちょっとクリア.
    それはすべてそこにある!
    その後、ファンシープロキシのものだけでcls 好きになるglobal .
    function getCurrent () {
        return current
    }
    module.exports = new Proxy( getCurrent, {
        get ( obj, prop ) {
            if ( prop === '$hold' ) return function(hold) {
                current[HOLD] = !!hold
            }
            if( prop=== '$init') return function(fn) {
                current && (current[HOLD] = true)
                if(fn) {
                    return function(...params) {
                        current && (current[HOLD] = true)
                        return fn(...params)
                    }
                }
            }
            if ( current ) {
                return current[ prop ]
            }
    
        },
        set ( obj, prop, value ) {
            if ( current ) {
                current[ prop ] = value
            }
            return true
        },
        has ( obj, prop ) {
            return prop in current
        },
    } )
    
    
    このプロパティを設定すると、現在のコンテクストで現在のコンテキストに設定されます.ゲットアンドhas は逆です.
    あなたは電話することができますcls() 現在のオブジェクトを取得するには

    デモ
    下のサンドボックスは、これを実装し、非常に退屈なページのための急行サーバーを提供します.あなたが通過しないならば?トークン=マジック?token = nosomagicその後、それは認証されません.それ以外の場合は、何が起こるかを制御する許可定義でCLSをどのように飾るかを見ることができます.