【问题标题】:Behavior of require in node.jsnode.js 中 require 的行为
【发布时间】:2014-01-12 17:55:40
【问题描述】:

我目前有一个包含以下内容的数据库连接模块:

var mongodb = require("mongodb");
var client = mongodb.MongoClient;
client.connect('mongodb://host:port/dbname', { auto_reconnect: true },
      function(err, db) {
         if (err) {
            console.log(err);
         } else {
            // export db as member of exports
            module.exports.db = db;
         }
      }
);

然后我可以通过以下方式成功访问它:

users.js

var dbConnection = require("./db.js");
var users = dbConnection.db.collection("users");
users.find({name: 'Aaron'}).toArray(function(err, result) {
  // do something
});

但是,如果我改为导出module.exports = db,即尝试将exports 对象分配给db 对象而不是使其成为导出的成员,并尝试通过@987654327 在users.js 中访问它@对象未定义,为什么?

如果是因为建立连接有延迟(不应该require() 等到模块完成其代码运行后再分配 module.exports 的值?),那么为什么这些示例都不起作用?

one.js

setTimeout(function() {
   module.exports.x = {test: 'value'};
}, 500);

两个.js

var x = require("./one");
console.log(x.test);

one.js

setTimeout(function() {
   module.exports.x = {test: 'value'};
}, 500);

两个.js

setTimeout(function() {
   var x = require("./one");
   console.log(x.test);
}, 1000);

运行$ node two.js 在这两种情况下都会打印undefined 而不是value

【问题讨论】:

    标签: javascript node.js mongodb express


    【解决方案1】:

    这里有3个关键点需要理解,然后我会详细解释。

    1. module.exports 是一个对象,对象通过 JavaScript 中的引用副本传递。
    2. require 是一个同步函数。
    3. client.connect 是一个异步函数。

    正如你所建议的,这是一个时间问题。 node.js 无法知道 module.exports 稍后会发生变化。那不是问题。它怎么会知道?

    require运行时,它会根据你输入的路径找到满足其要求的文件,读取并执行,缓存module.exports,以便其他模块可以require同一个模块而不必重新初始化它(这会弄乱变量范围等)

    client.connect 是一个异步函数调用,因此在执行后,模块完成执行,require 调用存储了 module.exports 引用的副本并将其返回给 users.js。然后你设置module.exports = db,但为时已晚。您将 module.exports 引用替换为对 db 的引用,但节点 require 缓存中的模块导出指向旧对象。

    最好将 module.exports 定义为一个函数,该函数将获取连接,然后将其传递给回调函数,如下所示:

    var mongodb = require("mongodb");
    var client = mongodb.MongoClient;
    
    module.exports = function (callback) {
        client.connect('mongodb://host:port/dbname', { auto_reconnect: true },
              function(err, db) {
                 if (err) {
                    console.log(err);
                    callback(err);
                 } else {
                    // export db as member of exports
                    callback(err, db);
                 }
              }
        )
    };
    

    警告:虽然它超出了此答案的范围,但请非常小心上面的代码,以确保正确关闭/返回连接,否则会泄漏连接。

    【讨论】:

    • 为什么设置module.exports.db = db 有效?无论我将其设置为module.exports 还是module.exports.db,都会发生同样的事情吗?
    • 这是不同的,因为 module.exports 是对一个对象的引用,而您并没有将其更改为引用不同的对象。相反,您正在改变现有对象。
    【解决方案2】:

    是的,dbConnection.db 未定义,因为连接是异步建立的,这意味着根据定义节点代码将继续执行,而无需等待数据库连接建立。

    难道不应该 require() 等到模块完成其代码运行后再分配 module.exports 的值吗?

    不,它只是不能那样工作。 require 用于始终存在的代码。数据库连接不是代码,并不总是存在。最好不要混淆这两种类型的资源以及如何从您的程序中引用它们。

    【讨论】:

    • 我很想知道设置module.exportsmodule.exports.db 之间的区别是什么,因为尽管有延迟,后者对我来说效果很好。我理解前者不起作用的原因,但不明白为什么module.exports.db 不会受到同样的命运。
    【解决方案3】:

    不应该 `require() 等到模块完成运行它的代码 在分配 module.exports 的值之前?

    module.exports.db在回调中设置,这个操作是异步的,所以在user.js你不能得到db.collection。 在connect回调中add集合会更好。

    您可以使用this 答案更改您的代码并在其他模块中使用shared connection

    【讨论】:

    • 如果我将它设置为module.exports.db,我可以在user.js 中获取它,如果我尝试将它设置为module.exports,这只是一个问题。
    【解决方案4】:

    问题是什么?这就是require 的工作方式 - 它同步获取模块并将导出传递给您。

    您的“等到代码运行”的建议可以通过两种方式回答:

    1. 它一直等到代码运行。 setTimeout 已成功完成。学习将面向未来的异步回调与实际线程分开。
    2. 如果您的意思是“直到所有异步回调都运行”,那是无稽之谈 - 如果其中一些根本没有运行怎么办,因为它等待,我不知道,鼠标点击,但用户没有附上鼠标? (你甚至如何定义“所有代码都已运行?”每条语句至少运行一次?if (true) { thisruns(); } else { thiswontrunever(); } 呢?)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-14
      • 1970-01-01
      • 1970-01-01
      • 2020-10-10
      • 1970-01-01
      • 2012-02-15
      • 1970-01-01
      • 2023-02-19
      相关资源
      最近更新 更多