2013-05-06 67 views
3

在節點中,我看到模塊中全局變量初始化的變量越來越多地被請求混淆[一個請求所做的更改會影響另一個請求]。 對於防爆:模塊內的節點js全局模塊

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); 
} 

按我的設想,printName功能模塊從 '一' 是每次執行新的出口請求命中節點,並且每次都會有不同的作用域對象。

因此,在模塊內部擁有全局內容不會影響他們跨請求。

但我明白事實並非如此。任何人都可以解釋節點如何以特定的方式處理函數的模塊導出[它處理緩存模塊導出對象的範圍],以及如何克服模塊內跨請求的共享全局變量?

編輯[我們對每個請求執行異步任務]: 在我們的現場系統中有快速請求。基本上查詢redis並響應請求。我們看到錯誤的響應映射到錯誤的請求(響應[存儲在模塊的全局變量中],redis查找錯誤地映射到diff req)。而且我們也有一些默認值作爲可以根據請求參數重寫的全局變量。這也是搞砸了

+0

這是發生在快速請求,還是有幾秒鐘的延遲,它仍然發生? – legacy 2013-05-08 14:08:54

+0

隨着我們現場系統的快速請求。基本上查詢redis並響應請求。我們看到錯誤的響應映射到錯誤的請求(響應[存儲在模塊的全局變量中],redis查找錯誤地映射到diff req)。而且我們也有一些默認值作爲可以根據請求參數重寫的全局變量。這也是搞砸了 – Tamil 2013-05-08 14:26:43

回答

0

模塊被設計爲運行一次並緩存該模塊,結合節點的異步性質意味着大約50%的時間res.end('Hi '+a)a = name(因爲已知)之前執行。

最終歸結爲JavaScript的一個簡單事實:全局變量是邪惡的。除非不被請求覆蓋,否則我不會使用全局。

+0

在給出的例子中,'res.end'調用將永遠不會執行,然後分配給'a'。 – josh3736 2013-05-08 15:40:06

3

這真的取決於您在過程中是否指定了姓名。

如果在給調用requestListener分配名稱之間有一個異步方法,那麼即使node.js是單一的,我們也會有「競爭條件」(IE兩個線程同時更改同一個對象) -threaded。
這是因爲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對象,如果你確實使用它們,它們應該是:不可變或者獨立的。

全局對象不應該用於在函數之間傳遞狀態(這就是您正在做的)。

你的問題的解決方案應該是將全局名稱放在一個對象中,建議的地方在request對象內部,它被傳遞給請求處理pipelie中的所有大部分函數(這就是connect.js, express.js和所有中間件都在這樣做),或者在一個會話中(請參閱connect。js會話中間件),這將允許您在來自同一用戶的不同請求之間持續數據。

11

理解發生的事情的第一步是瞭解幕後發生的事情。從語言的角度來看,節點模塊沒有什麼特別之處。當你使用require時,'魔術'來自於節點如何從磁盤加載文件。

當您調用require時,節點要麼從磁盤同步讀取,要麼返回模塊的緩存導出對象。當讀取文件,它遵循a set of somewhat complex rules準確地確定其文件讀取,但是一旦它有一個路徑:

  1. 檢查是否存在require.cache[moduleName]。如果確實如此,則返回並停止。
  2. code = fs.readFileSync(path)
  3. Wrap(串連)code用字符串(function (exports, require, module, __filename, __dirname) { ... });
  4. eval您的包裹代碼和調用匿名包裝函數。

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

require同一模塊,在下一次的節點只返回緩存exports對象。 (這是一件非常好的事情,因爲初始的加載過程是緩慢的,同步的。)

所以,現在你應該可以看到:

  • 模塊中的頂級代碼只執行一次。
  • ,因爲它是一個匿名函數實際執行:
    • 「全局」變量實際上並沒有全局的(除非你明確地分配給global或沒有範圍的與var變量)
    • 這是怎樣一個模塊獲取本地範圍。

在你的榜樣,你require模塊爲每個請求但你實際上跨越因爲上述的模塊緩存機制的所有requrests共享相同的模塊範圍。每次調用printName在其範圍鏈中共享相同的a(即使printName本身在每次調用時都獲得一個新的範圍)。

現在在你的問題的文字代碼中,這並不重要:你設置了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; 

我們這裏有一個問題,因爲控制確實離開printName。回調最終會觸發,但另一個請求在此期間改變了a

你可能想要更多的東西是這樣的:

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

+0

使我的理解更加清晰。我已經用urs的解決方案來解決範圍問題。但要確保它發生:) TQ的一個非常明確的答案 – Tamil 2013-05-09 09:55:00