Node.js中module.exports异步初始化问题:pool.query不是函数
我正在使用PostgreSQL、Express和Node.js构建一个简易Todo应用,遇到了module.exports相关的问题。
我的index.js代码如下:
const express = require("express"); const cors = require("cors"); const pool = require("./db"); const app = express(); const PORT = 5000; app.use(cors()); app.use(express.json()); console.log("2"); app.get("/todos", async (req, res) => { try { const result = await pool.query("SELECT * FROM todo"); res.json(result.rows); } catch (err) { console.log(err); } });
我的db.js代码如下:
const fs = require("fs").promises; const path = require("path"); const pgtools = require("pgtools"); const { Pool } = require("pg"); const dB = "perntodo"; const config = { user: "postgres", password: "my_pass", port: 5432, host: "localhost", }; const createDB = async () => { try { await pgtools.createdb(config, dB); console.log(`database ${dB} created`); } catch (err) { console.log(err); } }; createDB() .then(async () => { const pool = new Pool({ ...config, database: dB, }); const schemaPath = path.join(__dirname, "database.sql"); const schema = await fs.readFile(schemaPath, "utf8"); await pool.query(schema); return pool; }) .then((pool) => { console.log("1"); module.exports = pool; }) .catch((err) => console.log(err));
database.sql仅包含简单的建表语句。
我遇到的问题是:module.exports在pool被new Pool实例化为对象前就执行了,导致index.js中出现错误:TypeError: pool.query is not a function。通过console.log发现,日志"2"先于"1"执行,说明Promise链存在问题。
我对db.js的控制流理解如下:首先调用createDB()函数创建perntodo数据库(不存在时),数据库创建完成后执行第一个.then()块,创建新的Pool并通过pool.query()将SQL schema文件加载到数据库,最后执行第二个.then()块将pool对象设为模块导出内容,确保应用其他部分能使用该对象。
想请教我的Promise链中到底存在什么错误?
问题根源
Node.js的模块加载是同步的:当index.js里执行require('./db')时,Node会立即同步执行db.js里的所有代码,但你的module.exports = pool是放在Promise的.then()回调里的——Promise回调是异步执行的,这意味着在模块加载完成时,module.exports还是默认的空对象,而非你期望的Pool实例。所以index.js拿到的pool是个空对象,自然没有query方法,也就抛出了错误。
日志"2"先于"1"执行,就是因为同步加载db.js时,Promise链还没走到.then()里的console.log("1"),而index.js里的console.log("2")是同步代码,会先执行。
解决方案
不能在异步回调里赋值module.exports,而是要导出一个Promise,让index.js等待这个Promise resolved后再拿到Pool实例。
修改db.js:
const fs = require("fs").promises; const path = require("path"); const pgtools = require("pgtools"); const { Pool } = require("pg"); const dB = "perntodo"; const config = { user: "postgres", password: "my_pass", port: 5432, host: "localhost", }; const createDB = async () => { try { await pgtools.createdb(config, dB); console.log(`database ${dB} created`); } catch (err) { // 如果数据库已存在,忽略错误(pgtools会抛出数据库已存在的错误) if (err.code !== "42P04") { console.log(err); throw err; } } }; // 导出一个Promise,resolve后返回Pool实例 module.exports = (async () => { await createDB(); const pool = new Pool({ ...config, database: dB, }); const schemaPath = path.join(__dirname, "database.sql"); const schema = await fs.readFile(schemaPath, "utf8"); await pool.query(schema); console.log("1"); return pool; })();
修改index.js:
需要等待db.js导出的Promise完成,才能拿到可用的Pool实例:
const express = require("express"); const cors = require("cors"); const app = express(); const PORT = 5000; app.use(cors()); app.use(express.json()); // 等待Pool实例初始化完成 require("./db") .then((pool) => { console.log("2"); app.get("/todos", async (req, res) => { try { const result = await pool.query("SELECT * FROM todo"); res.json(result.rows); } catch (err) { console.log(err); res.status(500).json({ error: err.message }); } }); // 初始化完成后再启动服务器 app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); }) .catch((err) => { console.error("Failed to initialize database pool:", err); process.exit(1); });
关键说明:
- db.js里立即执行一个异步函数,返回Promise,这个Promise会在数据库创建、Pool初始化、Schema执行完成后resolve出Pool实例。
- index.js不再直接require得到pool,而是等待这个Promise resolve后再使用pool,同时确保服务器在数据库准备好之后再启动,避免请求进来时数据库还没初始化完成。
- 处理了数据库已存在的错误(pgtools创建已存在的数据库会抛出
42P04错误),避免不必要的报错。
内容的提问来源于stack exchange,提问作者Shreyas Nair

