使用async/await改写Node.js Express登录逻辑并优化错误处理
如何用async/await精简Express登录逻辑并实现精准错误处理
这个问题我之前也碰到过——用async/await摆脱回调地狱后,又掉进了错误处理的坑里。既要保留async/await的简洁性,又要精准识别每个步骤的错误,下面几个方案能完美解决你的问题:
方案1:自定义错误类型(最规范的长期维护方案)
创建不同的错误类来区分错误来源和类型,这样在catch块里可以通过instanceof快速判断错误类型,返回对应的响应。
首先定义自定义错误类:
class ValidationError extends Error { constructor(message, details) { super(message); this.name = 'ValidationError'; this.statusCode = 403; this.details = details; } } class DbError extends Error { constructor(message, details) { super(message); this.name = 'DbError'; this.statusCode = 500; this.details = details; } } class AuthError extends Error { constructor(message) { super(message); this.name = 'AuthError'; this.statusCode = 403; } } class JwtError extends Error { constructor(message, details) { super(message); this.name = 'JwtError'; this.statusCode = 500; this.details = details; } }
然后把原来的回调式工具函数转成Promise风格(用util.promisify或手动封装),并抛出对应自定义错误:
const util = require('util'); // 把回调函数转成Promise const checkIfEmailAndPasswordAreSet = util.promisify(originalCheckFn); const findUserByEmail = util.promisify(originalFindUserFn); // 手动封装示例(如果原函数不适合直接promisify) function checkUserPassword(user, password) { return new Promise((resolve, reject) => { originalCheckPasswordFn(user, password, (error, result) => { if (error) { return reject(new DbError('bcrypt error', error)); } resolve(result); }); }); }
最后重写登录逻辑,只用一个try/catch:
async function loginHandler(req, res) { const { email, password } = req.body; try { await checkIfEmailAndPasswordAreSet(email, password); const user = await findUserByEmail(email); if (!user) throw new AuthError('user not found'); const result = await checkUserPassword(user, password); if (!result) throw new AuthError('incorrect password'); await updateUserLastLoggedIn(user); const token = await generateSessionToken(user); return successResponse(user, token); } catch (e) { // 根据错误类型返回对应响应 if (e instanceof ValidationError || e instanceof AuthError) { return errorResponse(e.statusCode, e.message, e.details); } if (e instanceof DbError || e instanceof JwtError) { return errorResponse(e.statusCode, e.message, e.details); } // 兜底处理未知错误 return errorResponse(500, 'unknown error', e); } }
方案2:给错误添加自定义属性(轻量快速方案)
如果不想创建多个错误类,可以直接给Error实例添加自定义属性,比如source、statusCode,在catch里根据这些属性判断:
修改工具函数的Promise封装:
function checkIfEmailAndPasswordAreSet(email, password) { return new Promise((resolve, reject) => { originalCheckFn(email, password, (error, response) => { if (error) { const err = new Error('validation error'); err.source = 'checkIfEmailAndPasswordAreSet'; err.statusCode = 403; err.details = error; return reject(err); } resolve(response); }); }); }
然后在catch块里处理:
async function loginHandler(req, res) { const { email, password } = req.body; try { // ... 同之前的业务逻辑 ... } catch (e) { // 处理自定义抛出的认证错误 if (e.message === 'user not found' || e.message === 'incorrect password') { return errorResponse(403, e.message); } // 根据source区分工具函数错误 switch(e.source) { case 'checkIfEmailAndPasswordAreSet': return errorResponse(e.statusCode, e.message, e.details); case 'findUserByEmail': case 'updateUserLastLoggedIn': return errorResponse(500, 'db error', e.details); case 'checkUserPassword': return errorResponse(500, 'bcrypt error', e.details); case 'generateSessionToken': return errorResponse(500, 'jwt error', e.details); default: return errorResponse(500, 'unknown error', e); } } }
方案3:封装异步操作返回{ error, data }(无try/catch直观方案)
如果不想用try/catch,可以把每个异步操作封装成返回{ error, data }的函数,直接判断error即可:
async function wrapAsync(fn, ...args) { try { const data = await fn(...args); return { error: null, data }; } catch (error) { return { error, data: null }; } } async function loginHandler(req, res) { const { email, password } = req.body; const { error: validationError } = await wrapAsync(checkIfEmailAndPasswordAreSet, email, password); if (validationError) { return errorResponse(403, 'validation error', validationError); } const { error: dbError, data: user } = await wrapAsync(findUserByEmail, email); if (dbError) return errorResponse(500, 'db error', dbError); if (!user) return errorResponse(403, 'user not found'); const { error: bcryptError, data: result } = await wrapAsync(checkUserPassword, user, password); if (bcryptError) return errorResponse(500, 'bcrypt error', bcryptError); if (!result) return errorResponse(403, 'incorrect password'); // 后续步骤同理... const token = await generateSessionToken(user); return successResponse(user, token); }
这个方案没有嵌套的try/catch,代码线性清晰,也能精准处理每个步骤的错误,缺点是比自定义错误类多了几行判断,但胜在直观。
总结
- 如果项目需要长期维护,**方案1(自定义错误类)**是最规范的,符合Node.js错误处理最佳实践,扩展性强。
- 如果是小型项目或快速迭代,方案2或3更轻量,不需要额外定义类,能快速实现需求。
内容的提问来源于stack exchange,提问作者Kasheftin
相关产品推荐
相关产品推荐

