Lambda嵌套Promise中bcrypt.compare返回pending的解决方法
问题场景
- Lambda函数通过Mongoose连接MongoDB,接收前端传入的用户名、密码后,先查询匹配用户,再调用bcrypt比对加密存储的密码完成校验。
- 代码运行时打印
match变量得到{<promise pending>},由于密码校验逻辑写在Promise回调内部,无法直接添加await获取比对结果。
原问题代码:
require("dotenv").config(); const mongoose = require("mongoose"); const User = require('../server/models/user'); const bcrypt = require('bcryptjs') exports.handler = async (event, context) => { context.callbackWaitsForEmptyEventLoop = false; mongoose.connect(`${process.env.MONGO_URI}`,{ useNewUrlParser: true, useUnifiedTopology: true, } ); let {name, password} = JSON.parse(event.body); let fd = await new Promise((res,rej)=>User.findOne( { name:name, }, (err, user) => { if (err) { rej({ statusCode: 500, body: JSON.stringify({ msg: err.message }), }); } else { //==> const match = await bcrypt.compare(password, JSON.stringify(user.password)); if (match) { let statusCode = user && match? 200:405 res({ statusCode, headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "application/json", }, body: JSON.stringify({ user}), }); } } } )); return fd };
解决方案
不要混用Mongoose回调写法和Promise,直接用Mongoose原生支持的Promise API配合async/await写线性逻辑,从根源上避免回调嵌套导致的await无法使用问题,同时补全原代码的逻辑漏洞。
改写后的可运行代码:
require("dotenv").config(); const mongoose = require("mongoose"); const User = require('../server/models/user'); const bcrypt = require('bcryptjs') // 缓存数据库连接,避免Lambda每次触发都新建连接 let dbConnection = null; exports.handler = async (event, context) => { context.callbackWaitsForEmptyEventLoop = false; // 复用已有连接,降低冷启动耗时、减少MongoDB连接数压力 if (!dbConnection) { dbConnection = await mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }); } const { name, password } = JSON.parse(event.body); try { // 直接await查询结果,无需手动包裹Promise const user = await User.findOne({ name }); // 补全用户不存在的分支逻辑 if (!user) { return { statusCode: 404, headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "application/json", }, body: JSON.stringify({ msg: "用户不存在" }) } } // 直接await bcrypt比对结果,注意不要给哈希值加JSON.stringify const isPasswordMatch = await bcrypt.compare(password, user.password); const statusCode = isPasswordMatch ? 200 : 405; return { statusCode, headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "application/json", }, body: JSON.stringify( isPasswordMatch ? { user } : { msg: "密码错误" } ) } } catch (err) { // 统一捕获查询、密码比对过程中的异常 return { statusCode: 500, headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "application/json", }, body: JSON.stringify({ msg: err.message }) } } };
关键修改说明
- 移除手动包裹的Promise层和Mongoose回调写法,直接
awaitMongoose查询返回的原生Promise,消除回调嵌套,bcrypt.compare可以直接在async函数流程中加await拿到布尔结果,不会出现pending状态。 - 修复原代码的逻辑错误:去掉
user.password外层多余的JSON.stringify,Mongoose查询返回的password字段本身就是字符串,转JSON会额外包裹引号导致密码比对永远失败;补全用户不存在、密码错误、运行时异常的分支处理,避免出现空指针报错、无返回值的问题。 - 新增数据库连接缓存逻辑:Lambda执行环境会复用热实例,缓存连接可以避免每次触发函数都新建Mongo连接,大幅降低冷启动耗时,也不会打满MongoDB的最大连接数。
如果一定要保留原有Promise+回调的写法,只需要把
new Promise传入的执行函数标记为async即可在回调内部正常使用await,但这种混用回调和Promise的写法维护成本高,非常不推荐。
内容的提问来源于stack exchange,提问作者Jenny
相关产品推荐
相关产品推荐

