Node.js async未resolve、Mongo游标forEach执行后程序挂起问题
问题产生原因
done提前打印:run()的调用链写法错误,.then()要求传入一个函数作为回调,等Promise resolve后执行,但你写的是.then(console.log('done')),这行代码会在JS引擎执行到这一行时立刻同步调用console.log('done'),再把它的返回值undefined作为参数传给.then(),完全没有等待run()内部逻辑执行,所以done会比业务逻辑的输出更早出现。- 逻辑执行顺序混乱、
await失效:你用的MongoDB驱动自带的cursor.forEach方法,本身不支持等待传入的async回调返回的Promise。你写的await cursor.forEach(...)实际上只是等待forEach本身同步执行完遍历触发的动作,完全不会等待每个doc回调里的异步操作(查商品库、发支付请求、更新支付状态)执行完成,相当于这些异步操作都是"悬空"的,外层run()的Promise状态不会等它们完成就变更。控制台输出2次test,是因为forEach遍历到2条文档时,会同步执行回调里的第一行console.log('test'),但后面的await逻辑会被丢到事件队列里,没有被等待。 - 进程持续挂起不退出:所有业务逻辑跑完后,你没有主动调用
client.close()关闭MongoDB客户端连接,连接池会一直保持存活,Node.js的事件循环不会清空,自然不会自动终止进程。另外回调里的异步操作如果抛出异常,因为没有被外层await监听,会变成未捕获的Promise异常,也可能导致进程卡住。
修复方案
- 修正Promise链的回调写法:把
done的打印逻辑包在函数里传给.then(),确保run()所有逻辑执行完成后再触发;所有异常都能被外层的catch正常捕获。 - 替换不支持async等待的
cursor.forEach,两种常用写法可选,都能保证每一条文档的异步处理逻辑按顺序执行、被外层await等待:- 数据量不大时,用
await cursor.toArray()把所有查询到的文档转成数组,再用普通for...of循环遍历处理,写法简单直观。 - 数据量较大不想全量加载到内存时,直接用
for await...of异步迭代cursor,逐条拉取文档处理,内存占用更低。
- 数据量不大时,用
- 所有业务逻辑执行完成后,主动调用
client.close()关闭MongoDB连接,让进程正常退出。
修正后的核心参考代码:
async function successfullyPaid(doc){ return await updateMongoDBStuff(); } async function run() { // 异步方法必须加await等待执行完成 await findMongoDBstuff(); // 写法1:全量拉取文档转数组后遍历,适合数据量小的场景 // const docs = await cursor.toArray(); // for (const doc of docs) { // 写法2:直接异步迭代游标,适合大数据量场景,内存占用更低 for await (const doc of cursor) { console.log('test') if (doc.plan.nextPayment <= getUnixTimeSeconds()) { if (parseInt(doc.plan.remaining) >= 0){ const allProds = client.db('ProductsDB').collection('allProds') const prod = await allProds.findOne({}) const amount = prod.recurring.periods[doc.plan.period] const data = { "transaction": { "amount" : "1000", "payment_method_token": `visa_card`, "currency_code": "USD", "retain_on_success": true, "email" : doc.client.email } } const response = await superagent.post(`https://example.com`) .send(data) .set('accept', 'json') .auth('xxxxxxxxxxxxxxxxxxxxxxxxx', 'xxxxxxxxxxxxxxxxxxxxxxxxxx'); if (response.body.transaction.succeeded == true){ await successfullyPaid(doc) } else { console.log(response) } } } } } // 修正then/catch回调写法,执行完成后主动关闭数据库连接 run() .catch((error)=>{ console.log(error); }) .then(async ()=>{ console.log('done') await client.close() })
内容的提问来源于stack exchange,提问作者Vibeman1987
相关产品推荐
相关产品推荐

