为何在save()回调中无法使用ctx.body?find()回调却正常
问题原因分析与解决方案
这问题我之前踩过坑,核心原因是你把async/await和传统回调函数混着用了,而且对Mongoose的API调用逻辑理解有偏差~
为什么find()的回调里ctx.body看似生效?
你写的userInfo.find({ username: username }, function(err, doc) {...})这种带回调的写法,其实不会返回Promise,所以前面的await根本没起到等待异步操作完成的作用。之所以ctx.body能生效,大概率是因为find操作比较快,在Koa框架发送响应之前,回调函数就执行完并设置了ctx.body——但这其实是不可靠的,如果数据库查询慢一点,同样会出现ctx.body不生效的情况。
为什么save()的回调里ctx.body完全无效?
和find的情况类似,ui.save(function(err, doc) {...})带回调时也不会返回Promise,await对它无效。代码会直接跳过save的异步操作,执行后面的await next()。Koa的中间件流程一旦走到next(),后续可能会触发响应发送的逻辑,等save的回调执行时,响应已经发送给客户端了,这时候再设置ctx.body自然不会有任何效果。
正确的写法:用Promise风格+await
Mongoose从4.0版本开始就支持Promise API了,我们完全可以抛弃回调,用await等待异步操作完成,这样就能保证在响应发送前设置好ctx.body:
修正后的/register/isNameUsed接口
router.post("/register/isNameUsed", async (ctx, next) => { let username = ctx.request.body.username; try { // 不带回调,直接await等待查询结果 const doc = await userInfo.find({ username: username }); ctx.body = { isNameUsed: doc.length > 0 }; } catch (err) { console.log(err); ctx.body = { error: "查询失败", message: err.message }; } await next(); });
修正后的/register接口
router.post("/register", async (ctx, next) => { let username = ctx.request.body.name; let password = ctx.request.body.password; var ui = new userInfo({ username, password }); try { // 不带回调,await等待保存完成 const doc = await ui.save(); ctx.body = { registerSuccess: true }; } catch (err) { console.log(err); ctx.body = { registerSuccess: false, message: "注册失败:" + err.message }; } await next(); });
总结一下
- 给Mongoose方法传递回调函数时,方法不会返回Promise,
await无法等待异步操作完成,代码会提前执行next()导致响应提前发送。 - 始终使用Mongoose的Promise风格API,配合
await控制异步流程,才能保证ctx.body在响应发送前被正确设置。
内容的提问来源于stack exchange,提问作者takeItIzzy
相关产品推荐
相关产品推荐

