使用async eachSeries时mongoose findOneAndUpdate单次迭代偶发执行两次问题
解决async eachSeries中Mongoose findOneAndUpdate偶发执行两次的问题
我之前在维护旧Node.js项目的时候碰到过类似的偶发问题,结合你的代码和运行环境(Node.js v6.10 + Mongoose),大概率是代码细节或者依赖版本的问题,给你梳理几个可能的原因和解决办法:
1. 代码中的明显错误导致的逻辑异常
先看你代码里的几个细节问题,这些可能是触发偶发问题的诱因:
- 查询条件错误:你在
findOneAndUpdate里用了整个数组a作为查询条件,而不是当前迭代的doc。虽然数组只有一个元素,但Mongoose解析查询条件时可能出现偶发的异常匹配,导致更新逻辑被重复触发。应该改成用当前迭代的文档对象:// 把 Foo.findOneAndUpdate(a, ...) 改成 Foo.findOneAndUpdate(doc, { user_last: "bar" }, { upsert: true, new: true }, (err, updatedDoc) => { ... }) - done函数调用错误:async的
eachSeries迭代器的done函数,第一个参数只能是错误对象(成功时传null或直接不传)。你在成功时调用done(doc),会把查询结果当成错误传入,这会导致async的内部逻辑出现异常,偶发触发重复迭代。正确的调用方式是:if (err) return done(err); return done(); // 成功时无需传结果,eachSeries不处理单条迭代的返回值 - 变量名冲突+逻辑矛盾:你在
findOne的回调里把结果也命名为doc,和迭代器的参数重名,容易导致逻辑混淆。另外你提到需求是“当文档存在时执行更新”,但却设置了upsert: true(不存在则插入),这和需求矛盾,建议把upsert改成false,同时先判断查询结果是否存在再执行更新:async.eachSeries(a, (item, done) => { Foo.findOne(item).lean(true).exec((err, foundDoc) => { if (err) return done(err); // 仅当文档存在时执行更新 if (foundDoc) { Foo.findOneAndUpdate({ _id: foundDoc._id }, { user_last: "bar" }, { new: true }, (err) => { if (err) return done(err); done(); }); } else { done(); } }); }, (err) => { if (err) console.error(err); console.log('completed'); });
2. 依赖版本的旧bug
Node.js v6.10和对应的旧版Mongoose(大概率是4.x系列)、async库可能存在一些偶发的回调重复触发bug:
- 升级Mongoose:Node.js v6.10支持的最新稳定Mongoose版本是4.13.x,升级到这个版本可以修复很多旧版的查询/更新回调异常问题。
- 升级async库:同样,把async库升级到支持Node.js v6的最新稳定版(2.6.x系列),旧版async的
eachSeries可能存在迭代器回调处理的偶发问题。
3. Mongoose中间件的重复触发
检查你的Foo模型是否注册了pre('findOneAndUpdate')或post('findOneAndUpdate')这类中间件,如果中间件里存在错误的逻辑(比如重复调用next或者触发额外的更新操作),也可能导致更新被执行两次。
4. 改用Promise/Async-Await简化逻辑
回调嵌套很容易出现这类隐蔽问题,Node.js v6.10可以通过Babel或者co库支持async-await,改用Promise风格的代码能大幅降低逻辑出错的概率:
const async = require('async'); const a = [ { user_name: "foo" } ]; async.eachSeries(a, async (item, done) => { try { const foundDoc = await Foo.findOne(item).lean().exec(); if (foundDoc) { await Foo.findOneAndUpdate({ _id: foundDoc._id }, { user_last: "bar" }, { new: true }).exec(); } done(); } catch (err) { done(err); } }, (err) => { if (err) console.error('更新失败:', err); console.log('completed'); });
内容的提问来源于stack exchange,提问作者made_in_india
相关产品推荐
相关产品推荐

