模块内的节点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)。而且我们也有一些默认值作为可以根据请求参数重写的全局变量。这也是搞砸了
模块被设计为运行一次并缓存该模块,结合节点的异步性质意味着大约50%的时间res.end('Hi '+a)
在a = name
(因为已知)之前执行。
最终归结为JavaScript的一个简单事实:全局变量是邪恶的。除非不被请求覆盖,否则我不会使用全局。
在给出的例子中,'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准确地确定其文件读取,但是一旦它有一个路径:
- 检查是否存在
require.cache[moduleName]
。如果确实如此,则返回并停止。 -
code = fs.readFileSync(path)
。 -
Wrap(串连)
code
用字符串(function (exports, require, module, __filename, __dirname) {
...});
-
eval
您的包裹代码和调用匿名包装函数。var module = { exports: {} }; eval(code)(module.exports, require, module, path, pathMinusFilename);
保存
module.exports
为require.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
。
使我的理解更加清晰。我已经用urs的解决方案来解决范围问题。但要确保它发生:) TQ的一个非常明确的答案 – Tamil 2013-05-09 09:55:00
这是发生在快速请求,还是有几秒钟的延迟,它仍然发生? – legacy 2013-05-08 14:08:54
随着我们现场系统的快速请求。基本上查询redis并响应请求。我们看到错误的响应映射到错误的请求(响应[存储在模块的全局变量中],redis查找错误地映射到diff req)。而且我们也有一些默认值作为可以根据请求参数重写的全局变量。这也是搞砸了 – Tamil 2013-05-08 14:26:43