在节点I中,看到模块内部全局初始化的变量正在混淆(一个请求完成的更改会影响另一个请求)。 对于Ex:
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)。此外,我们还有一些默认值作为全局变量,可以根据请求参数覆盖。这也搞砸了
答案 0 :(得分:11)
了解正在发生的事情的第一步是了解幕后发生的事情。从语言的角度来看,节点模块并没有什么特别之处。 “魔力”来自于require
时节点如何从磁盘加载文件。
当您调用require
时,节点可以同步从磁盘读取或返回模块的缓存导出对象。在读取文件时,它会跟随a set of somewhat complex rules确定读取哪个文件,但一旦有了路径:
require.cache[moduleName]
是否存在。如果是,请返回并停止。code = fs.readFileSync(path)
。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
模块 a 用于每个请求,但您实际上是在所有请求中共享相同的模块范围,因为模块上面概述了缓存机制。对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
内为每个请求获得一个全新且独立的范围。
答案 1 :(得分:3)
这实际上取决于您在流程中何时指定名称。
如果在为调用requestListener分配名称之间有一个异步方法,那么你就会有“竞争条件”(IE两个线程同时改变同一个对象),即使node.js是单线程的。
这是因为当异步方法在后台运行时,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对象,如果你使用它们,它们应该是:immutable或self-contained。
全局对象不应该用于在函数之间传递状态(这就是你正在做的事情)。
你的问题的解决方案应该是将名称全局放在一个对象中,建议的位置在请求对象内部,它被传递给请求处理管道中的所有大多数函数(这就是connect.js,express .js和所有中间件都在做),或者在一个会话中(参见connect.js会话中间件),它允许你在来自同一个用户的不同请求之间保存数据。
答案 2 :(得分:0)
模块被设计为运行一次并缓存模块,结合节点的异步性质意味着res.end('Hi '+a)
之前约有50%的时间a = name
执行(因为a已知)。
归根结底,它归结为JavaScript的一个简单事实:全球变量是邪恶的。我不会使用全局,除非它永远不会被请求覆盖。