依赖Node.js需要行为来实现Singletons是否安全?

时间:2018-04-04 11:43:06

标签: javascript node.js

假设我有一个像这样实现的模块:

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.jsrequire几次。 Node.js应该只执行一次代码,并始终传递数据库池的同一个实例。依赖Node.js require命令的这种行为是否安全?我想使用它,这样我就不需要实现依赖注入。

2 个答案:

答案 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语句解析的确切文件。