MongoClient.connect抽离独立文件后报Cannot read property 'db' of undefined如何解决
问题解答
1. 问题解决方法
现有写法的问题分析
你第一次抽离的database.js之所以报错,是因为CommonJS模块的导出是同步执行的,MongoClient.connect是异步方法,模块加载导出mongo_client时,异步连接还未完成,变量仍为undefined,后续异步回调中给mongo_client赋值也不会更新已经导出的值,所以app.js中拿到的一直是undefined。
你补充的写法能生效,是因为这次你一开始就导出了MongoClient实例对象(引用类型),后续异步调用connect方法只会修改实例的内部状态,不会改变实例的引用地址,所以服务启动完成后收到请求时,数据库连接一般已经初始化完成,就可以正常调用。但该写法存在隐患:如果服务刚启动、数据库还未完成连接时就收到请求,依然会报错。
推荐稳妥实现方案
第一步:修改config/database.js,导出单例获取方法
const MongoClient = require('mongodb').MongoClient; const { connection_string } = require('./environment_variables'); let clientInstance = null; // 导出异步获取MongoClient单例的方法 module.exports.getMongoClient = async () => { // 已有连接直接返回,保证单例 if (clientInstance) return clientInstance; const client = new MongoClient(connection_string); await client.connect(); clientInstance = client; return clientInstance; };
第二步:服务启动前先初始化数据库连接
不需要把所有业务逻辑塞到连接回调里,只需要把server.listen的逻辑放到异步初始化流程中即可,不影响express、socket.io的前置逻辑编写:
// app.js 前置逻辑正常编写 const express = require('express'); const app = express(); const http = require('http'); const server = http.createServer(app); const io = require('socket.io')(server); app.set('socketio', io); const { getMongoClient } = require('./config/database'); const PORT = 3000; // 中间件、路由注册逻辑正常写在这里 // ...... // 最后先保证数据库连接成功,再启动服务 (async () => { await getMongoClient(); server.listen(PORT, () => { console.log(`running on port ${PORT}`); }); })();
第三步:路由/中间件复用连接的两种方式
- 全局挂载到req对象(推荐):写一个通用中间件,把数据库实例挂载到每个请求的req上,所有路由都可以直接使用,不需要重复引入:
// app.js 路由注册前添加该中间件 app.use(async (req, res, next) => { req.mongoClient = await getMongoClient(); next(); }); // 任意路由中直接使用 app.get('/', async (req, res) => { const query = { username: 'my name' }; const collection = req.mongoClient.db('users').collection('users'); const result = await collection.findOne(query); res.json({ result }); }); - 直接引入调用:在需要用到数据库的路由文件中直接引入
getMongoClient方法调用即可,因为是单例,不会重复建立连接。
2. 单文件时未出现问题的原因
单文件场景下,mongo_client是当前文件的全局变量,你在中间件中调用mongo_client.db是请求进来时才执行的,而非模块加载阶段执行。通常服务启动完成、收到第一个请求时,数据库异步连接已经完成,mongo_client已经被正确赋值,所以不会报错。
而抽离到独立文件后,模块导出是在加载阶段同步执行的,此时异步连接还未完成,导出的就是undefined,后续修改原文件的mongo_client变量也不会更新已经导出的值,所以一直报错。
内容的提问来源于stack exchange,提问作者user1063287
相关产品推荐
相关产品推荐

