模块内的节点js全局模块

问题描述:

在节点中,我看到模块中全局变量初始化的变量越来越多地被请求混淆[一个请求所做的更改会影响另一个请求]。 对于防爆:模块内的节点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

模块被设计为运行一次并缓存该模块,结合节点的异步性质意味着大约50%的时间res.end('Hi '+a)a = name(因为已知)之前执行。

最终归结为JavaScript的一个简单事实:全局变量是邪恶的。除非不被请求覆盖,否则我不会使用全局。

+0

在给出的例子中,'res.end'调用将永远不会执行,然后分配给'a'。 – josh3736 2013-05-08 15:40:06

这真的取决于您在过程中是否指定了姓名。

如果在给调用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会话中间件),这将允许您在来自同一用户的不同请求之间持续数据。

理解发生的事情的第一步是了解幕后发生的事情。从语言的角度来看,节点模块没有什么特别之处。当你使用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