重复请求Express接口报Query was already executed错误原因
问题描述
我定义了如下Schema与Model:
const userSchema = new mongoose.Schema({ name: String, }) const UserModel = mongoose.model('User', userSchema, 'users')
我编写了如下Express中间件,接收一个query参数,对该参数执行await等待,将异步返回的结果挂载到req对象的gottenDocs属性上(即req.gottenDocs):
function getDocumentsMw(query) { return async (req, res, next) => { const dbRes = await query req.gottenDocs = dbRes next() } }
随后定义了如下路由:
app.get( '/users', getDocumentsMw(UserModel.find({})), (req, res, next) => { const gottenDoc = req.gottenDocs res.status(200).json({ status: 'success', data: gottenDoc, }) })
首次发起 GET "/users" 请求时,接口正常返回如下响应,无任何异常:
{ "status": "success", "data": [] }
但异常的是,第二次请求该路由时会抛出如下错误:
MongooseError: Query was already executed: User.find({})

请问该问题是什么原因导致的?是Node.js的bug吗?比如是否存在响应发送后未将函数调用移出调用栈的问题?
问题原因
这不是Node.js的bug,是代码写法逻辑错误导致的。核心原因是Mongoose的Query实例是带状态的一次性对象,你在路由初始化阶段就执行了UserModel.find({}),生成的同一个Query实例被所有请求复用,才触发了重复执行的报错。
具体执行逻辑拆解:
- Express服务启动加载路由时,会立刻执行
getDocumentsMw(UserModel.find({}))这行代码,此时UserModel.find({})就已经生成了一个固定的Mongoose Query实例,传入中间件工厂的就是这一个实例,不会在请求到来时重新生成。 - 第一次请求进入时,中间件对这个Query实例执行
await,触发查询执行,拿到结果后这个Query就会被Mongoose标记为已执行状态。 - 第二次及后续请求进入时,中间件还是对同一个已经执行过的Query实例执行
await,Mongoose检测到重复执行行为,就会直接抛出Query was already executed错误。
修复方案
不要在路由初始化阶段就生成Query实例,改为传入一个返回Query的工厂函数,每次请求到来时再调用函数生成全新的Query实例执行即可。
修改后的中间件代码:
// 入参改为接收返回Query的工厂函数,而非直接接收Query实例 function getDocumentsMw(queryFactory) { return async (req, res, next) => { // 每次请求到来时执行工厂函数,生成全新的Query实例 const dbRes = await queryFactory() req.gottenDocs = dbRes next() } }
路由调用处对应修改,把直接传入Query改为传入返回Query的箭头函数:
app.get( '/users', getDocumentsMw(() => UserModel.find({})), (req, res, next) => { const gottenDoc = req.gottenDocs res.status(200).json({ status: 'success', data: gottenDoc, }) })
修改后每次请求都会生成独立的Query实例,每个实例仅执行一次查询,就不会再出现重复执行的报错。
内容的提问来源于stack exchange,提问作者Islam Y-
相关产品推荐
相关产品推荐

