3

ノードでは、モジュール内でグローバルに初期化された変数が、リクエスト間で混同されていることがわかります[1つのリクエストによって行われた変更が他のリクエストに影響を与えます]。例:

a.js

var a;
function printName(req, res) {
  //get param `name` from url;
  a = name;
  res.end('Hi '+a);
}
module.exports.printName = printName;

index.js

//Assume all createServer stuffs are done and following function as a CB to createServer
function requestListener(req, res) {
  var a = require('a');
  a.printName(req, res);
}

私の仮定によると、モジュール「a」からエクスポートされたprintName関数は、新しいリクエストがノードにヒットするたびに実行され、毎回異なるスコープオブジェクトを持ちます。

したがって、モジュール内にグローバルなものを持っていても、リクエスト間でそれらに影響を与えることはありません。

しかし、そうではないことがわかります。ノードが特定の関数のモジュール エクスポートを処理する方法 [キャッシュされたモジュール エクスポート オブジェクトのスコープを処理する方法] と、モジュール内のリクエスト全体でこの共有グローバル変数を克服する方法を説明できる人はいますか?

編集 [リクエストごとに非同期タスクを実行します]: ライブ システムでの迅速なリクエストを使用します。これは基本的にredisにクエリを実行し、リクエストに応答します。間違った応答が間違った要求にマッピングされていることがわかります (redis ルックアップの [モジュール内のグローバル変数に格納された] 応答が、誤って diff req にマッピングされています)。また、リクエストパラメーターに基づいてオーバーライドできるグローバル変数としていくつかのデフォルト値があります。これも台無しになっています

4

3 に答える 3

11

何が起こっているのかを理解するための最初のステップは、舞台裏で何が起こっているのかを理解することです。言語の観点からは、ノード モジュールについて特別なことは何もありません。「魔法」は、ノードがディスクからファイルをロードする方法に由来しますrequire

を呼び出すとrequire、ノードはディスクから同期的に読み取るか、モジュールのキャッシュされたエクスポート オブジェクトを返します。ファイルを読み取るとき、どのファイルを読み取るかを正確に決定するために、やや複雑な一連のルールに従いますが、パスがあれば、次のようになります。

  1. 存在するかどうかを確認require.cache[moduleName]します。そうであれば、それを返して停止します。
  2. code = fs.readFileSync(path).
  3. code文字列でラップ(連結)(function (exports, require, module, __filename, __dirname) {...});
  4. evalラップされたコードを作成し、匿名ラッパー関数を呼び出します。

    var module = { exports: {} };
    eval(code)(module.exports, require, module, path, pathMinusFilename);
    
  5. として保存module.exportsしますrequire.cache[moduleName]

次にrequire同じモジュールを使用すると、ノードは単にキャッシュされたexportsオブジェクトを返します。(これは非常に良いことです。なぜなら、最初のロード プロセスは低速で同期的だからです。)

これで、次のように表示されるはずです。

  • モジュール内の最上位コードは 1 回だけ実行されます。
  • 実際には無名関数で実行されるため:
    • 「グローバル」変数は実際にはグローバルではありません (変数を明示的に割り当てるglobalか、変数のスコープを設定しない限りvar)
    • これは、モジュールがローカル スコープを取得する方法です。

あなたの例では、リクエストごとにarequireをモジュール化していますが、実際には、上記で概説したモジュール キャッシュ メカニズムにより、すべてのリクエストで同じモジュール スコープを共有しています。へのすべての呼び出しは、そのスコープ チェーンで同じものを共有します (呼び出しごとにそれ自体が新しいスコープを取得しますが)。printNameaprintName

質問にあるリテラルコードでは、これは問題ではありません。設定aしてから、次の行で使用します。コントロールは を離れることはないので、が共有されprintNameているという事実は関係ありません。a私の推測では、実際のコードは次のようになります。

var a;
function printName(req, res) {
  //get param `name` from url;
  a = name;
  getSomethingFromRedis(function(result) {
      res.end('Hi '+a);
  });
}
module.exports.printName = printName;

ここで問題が発生ますprintNameaコールバックは最終的に起動しますが、その間に別のリクエストが変更されました。

おそらく、次のようなものがもっと必要です。

a.js

module.exports = function A() {
    var a;
    function printName(req, res) {
      //get param `name` from url;
      a = name;
      res.end('Hi '+a);
    }

    return {
        printName: printName
    };
}

index.js

var A = require('a');
function requestListener(req, res) {
  var a = A();
  a.printName(req, res);
}

Aこのようにして、各リクエストの内部に新鮮で独立したスコープを取得します。

于 2013-05-08T16:28:57.947 に答える
3

プロセスのどの時点で名前を割り当てるかは、本当に異なります。

名前を requestListener の呼び出しに割り当てる間に async メソッドがある場合、node.js がシングルスレッドであっても、「競合状態」(同じオブジェクトを同時に変更する 2 つのスレッド) が発生します。
これは、非同期メソッドがバックグラウンドで実行されている間に、node.js が新しいリクエストの処理を開始するためです。

たとえば、次のシーケンスを見てください。

request1 starts processing, sets name to 1
request1 calls an async function 
node.js frees the process, and handles the next request in queue.
request2 starts processing, sets name to 2
request2 calls an async function
node.js frees the process, the async function for request 1 is done, so it calls the callback for this function.
request1 calls requestListener, however at this point name is already set to 2 and not 1.

Node.js で非同期関数を処理することは、マルチスレッド プログラミングと非常に似ているため、データをカプセル化するように注意する必要があります。一般に、Global オブジェクトの使用は避けるようにしてください。使用する場合は、不変または自己完結型のいずれかにする必要があります。

グローバルオブジェクトは、関数間で状態を渡すために使用しないでください (これはあなたが行っていることです)。

あなたの問題の解決策は、グローバルという名前をオブジェクト内に置くことです。提案された場所はリクエストオブジェクト内にあり、リクエスト処理パイプリーのほとんどすべての関数に渡されます(これはconnect.js、express.jsとすべてのミドルウェアが実行している)、またはセッション内 (connect.js セッション ミドルウェアを参照) で、同じユーザーからの異なる要求間でデータを永続化できます。

于 2013-05-08T14:51:39.870 に答える