假设我有一个像这样实现的模块:
const dbLib = require('db-lib');
const dbConfig = require('db-config');
const dbInstance = dbLib.createInstance({
host: dbConfig.host,
database: dbConfig.database,
user: dbConfig.user,
password: dbConfig.password,
});
module.exports = dbInstance;
此处创建并导出数据库连接池的实例。然后在整个应用中假设db-instance.js
为require
几次。 Node.js应该只执行一次代码,并始终传递数据库池的同一个实例。依赖Node.js require
命令的这种行为是否安全?我想使用它,这样我就不需要实现依赖注入。
答案 0 :(得分:1)
Node.js中需要的每个文件都是Singleton。
此外 - 要求是同步操作,它也可以确定性地工作。这意味着它是可预测的,并且将始终如此。
此行为不仅适用于require
- Node.js在Event-Loop中只有一个线程,它的工作原理如下(简化):
例如,假设此代码是文件infinite.js:
setTimeout(() => {
while(true){
console.log('only this');
}
}, 1000)
setTimeout(() => {
while(true){
console.log('never this');
}
}, 2000)
while(someConditionThatTakes5000Miliseconds){
console.log('requiring');
}
当你需要这个文件时,它首先注册到“doLater”第一个setTimeout到“1000ms被解析后”,第二个为“2000ms之后被解析”(注意它不是“1000ms后运行”)。 / p>
然后它运行while循环5000ms(如果有这样的条件)并且你的代码中没有其他任何事情发生。
5000ms后,需求完成,同步部分完成,Event Loop查找要执行的新任务。第一个看到的是setTimeout,延迟时间为1000毫秒(再一次 - 只需要1000毫秒标记为“可以通过事件循环”,但你不知道它何时会被运行)。
循环中存在无休止,因此您将在控制台中看到“仅此”。第二个setTimeout永远不会从Event-Loop中获取,因为它在2000ms之后被标记为“可以被采用”,但事件循环已经停留在永不停止的while循环中。
有了这些知识,您可以非常自信地使用require
(和其他Node.js方面)。
结论 - require
是同步的,确定性的。一旦它完成了需要文件(它的输出是一个带有导出方法和属性的对象,或者如果你不导出任何东西就是空对象),对该对象的引用将保存到Node.js核心内存中。当你需要来自其他地方的文件时,它首先查看核心内存,如果它在那里找到需要,它只使用对象的引用,因此永远不会执行两次。
POC:
创建文件infinite.js
const time = Date.now();
setTimeout(() => {
let i=0;
console.log('Now I get stuck');
while(true){
i++;
if (i % 100000000 === 0) {
console.log(i);
}
}
console.log('Never reach this');
}, 1000)
setTimeout(() => {
while(true){
console.log('never this');
}
}, 2000)
console.log('Prepare for 5sec wait')
while(new Date() < new Date(time + 5*1000)){
// blocked
}
console.log('Done, lets allow other')
然后使用
在同一文件夹中创建server.js.console.log('start program');
require('./infinite');
console.log('done with requiring');
使用node server
这将是输出(数字无止境):
start program
Prepare for 5sec wait
Done, lets allow other
done with requiring
Now I get stuck
100000000
200000000
300000000
400000000
500000000
600000000
700000000
800000000
900000000
答案 1 :(得分:1)
documentation of Node.js about modules解释说:
模块在第一次加载后进行缓存。这意味着(除其他外)每次调用
require('foo')
将获得完全相同的返回对象,如果它将解析为同一文件。多次调用
require('foo')
可能不会导致模块代码多次执行。
值得一提的是require
生成单身人士时的情况以及无法实现这一目标的原因(及其原因):
模块根据其解析的文件名进行缓存。由于模块可能会根据调用模块的位置(从
node_modules
文件夹加载)解析为不同的文件名,因此不能保证require('foo')
将始终返回完全相同的对象,如果它将解析到不同的文件。此外,在不区分大小写的文件系统或操作系统上,不同的已解析文件名可以指向同一个文件,但缓存仍会将它们视为不同的模块,并会多次重新加载该文件。例如,
require('./foo')
和require('./FOO')
会返回两个不同的对象,无论./foo
和./FOO
是否为同一个文件。
总而言之,如果您的模块名称在项目中是唯一的,那么您将始终获得单身人士。否则,当有两个具有相同名称的模块时,require
- 在不同位置使用该名称可能生成不同的对象。为了确保它们生成相同的对象(所需的Singleton),您必须以在两个地方解析为同一文件的方式refer the module。
您可以使用require.resolve()
找出require
语句解析的确切文件。