Node.js错误处理中间件未生效,应用遇错崩溃无响应求解
问题:Express错误处理中间件未捕获异步控制器异常,导致服务器崩溃
我在后端设置了errorHandlerMiddleware错误处理中间件来处理异常,但它未按预期工作:每次出现错误时,应用直接崩溃,服务器无法返回正确响应。推测是authController.js中抛出的异常未被server.js中的errorHandlerMiddleware捕获,从而导致服务器崩溃。
Error Handler
const errorHandlerMiddleware = (err, req, res, next)=>{ console.log(err) const defaultError = { statusCode: err.statusCode || 500, msg: err.message || "Something Went Wrong, Try Again Later", } if(err.name==="ValidationError"){ defaultError.statusCode = 400 defaultError.msg = Object.values(err.errors).map((item)=> item.message).join(', ') } if(err.code && err.code === 11000){ defaultError.statusCode = 400 defaultError.msg= `${Object.keys(err.keyValue)} has to be unique` } res.status(defaultError.statusCode).json({msg: defaultError.msg}) } export default errorHandlerMiddleware
Server
import express from "express"; const app = express(); dotenv.config(); import notFound from "./middleware/NotFound.js"; import errorHandlerMiddleware from "./middleware/errorHandlerMiddleware.js"; import authRouter from "./routes/authRouter.js"; import dotenv from "dotenv"; import connectDB from "./db/connect.js"; app.use(express.json()); app.get("/", (req, res) => { res.send("Welcome"); }); app.get("/api/v1", (req, res) => { res.json({ msg: "API" }); }); app.use("/api/v1/auth", authRouter); app.use(notFound); app.use(errorHandlerMiddleware); const port = process.env.PORT || 5000; const start = async () => { try { await connectDB(process.env.MONGO_URL); app.listen(port, () => { console.log(`Server is running on port ${port}`); }); } catch (error) { console.log(error); } }; start();
Auth Controller
import {BadRequestError, UnauthenticatedError} from "../errorPackage/index.js" import User from '../models/User.js' const register = async(req, res) =>{ const {name, email, password} = req.body if(!name || !email || !password){ throw new BadRequestError("Please Provide All Values") } const alreadyUser = await User.findOne({email}) if(alreadyUser){ throw new BadRequestError("Email In Use") } const user = await User.create(req.body) const token = user.createJWT() res.json({ user:{ name: user.name, email: user.email, }, token, }) console.log('register') } const login = async(req, res) =>{ const{email, password} = req.body if(!email || !password){ throw new BadRequestError("Please Provide All Values") } const user = await User.findOne({email}).select('+password') if(!user){ throw new UnauthenticatedError("Invalid Email") } const passwordMatch = await user.comparePassword(password) if(!passwordMatch){ throw new UnauthenticatedError("Invalid Credentials") } const token = await user.createJWT() user.password = undefined; res.status(200).json({user, token}) console.log('login success') } const updateUser = async(req, res) =>{ res.send('Update') } export {register, login, updateUser}
authRouter
import express from "express"; const router = express.Router() import { register, login, updateUser} from "../controllers/authController.js"; router.route('/register').post(register) router.route('/login').post(login) router.route('/updateUser').patch(updateUser) export default router
错误示例
当发送注册请求时使用已存在的邮箱,会出现如下错误:
[0] Server is running on port 5000 [0] file:///C:/Users/Bharat/Desktop/MERNPrj/controllers/authController.js:10 [0] throw new BadRequestError("Email In Use") [0] ^ [0] [0] BadRequestError: Email In Use [0] at register (file:///C:/Users/Bharat/Desktop/MERNPrj/controllers/authController.js:10:15) [0] at processTicksAndRejections (node:internal/process/task_queues:96:5) { [0] statusCode: 400 [0] } [0] [nodemon] app crashed - waiting for file changes before starting...
我猜测应用在服务器返回正确响应前就崩溃了。
解决方案
问题核心:Express 默认不会自动捕获async函数中抛出的异常,这些未被捕获的异常会直接终止 Node.js 进程,无法触发错误处理中间件。
提供两种修复方式:
方式一:用try/catch包裹异步逻辑,传递异常给next
修改authController.js中的异步函数,添加next参数并在catch块中传递错误:
const register = async(req, res, next) =>{ // 新增next参数 try { const {name, email, password} = req.body if(!name || !email || !password){ throw new BadRequestError("Please Provide All Values") } const alreadyUser = await User.findOne({email}) if(alreadyUser){ throw new BadRequestError("Email In Use") } const user = await User.create(req.body) const token = user.createJWT() res.json({ user:{ name: user.name, email: user.email, }, token, }) console.log('register') } catch (err) { next(err) // 将错误传递给错误处理中间件 } } const login = async(req, res, next) =>{ // 新增next参数 try { const{email, password} = req.body if(!email || !password){ throw new BadRequestError("Please Provide All Values") } const user = await User.findOne({email}).select('+password') if(!user){ throw new UnauthenticatedError("Invalid Email") } const passwordMatch = await user.comparePassword(password) if(!passwordMatch){ throw new UnauthenticatedError("Invalid Credentials") } const token = await user.createJWT() user.password = undefined; res.status(200).json({user, token}) console.log('login success') } catch (err) { next(err) // 将错误传递给错误处理中间件 } }
方式二:封装异步捕获工具函数(更简洁)
新建工具函数统一处理异步异常,避免重复写try/catch:
// utils/asyncWrapper.js const asyncWrapper = (fn) => { return async (req, res, next) => { try { await fn(req, res, next); } catch (err) { next(err); } }; }; export default asyncWrapper;
在authRouter.js中使用工具函数包裹控制器:
import express from "express"; const router = express.Router() import { register, login, updateUser} from "../controllers/authController.js"; import asyncWrapper from "../utils/asyncWrapper.js"; router.route('/register').post(asyncWrapper(register)) router.route('/login').post(asyncWrapper(login)) router.route('/updateUser').patch(asyncWrapper(updateUser)) export default router
原理说明
Express错误处理中间件仅捕获两类异常:
- 同步代码中直接抛出的异常
- 通过
next(err)手动传递的异常
async函数中抛出的异常会转化为Promise拒绝状态,若未被捕获或传递,会成为未处理的Promise拒绝,直接导致进程崩溃。上述两种方式都是将异步异常转化为next(err)的形式,让错误处理中间件能够捕获并返回规范响应。
内容的提问来源于stack exchange,提问作者Het Patel
相关产品推荐
相关产品推荐

