跳到主要内容

错误处理

本章节介绍 Express.js 中的错误处理机制和最佳实践。

错误处理中间件

Express 错误处理中间件有四个参数:(err, req, res, next)

app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: '服务器错误' });
});

基本错误处理

同步错误

app.get('/sync-error', (req, res) => {
throw new Error('同步错误');
});

app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});

异步错误

app.get('/async-error', (req, res, next) => {
setTimeout(() => {
try {
throw new Error('异步错误');
} catch (err) {
next(err);
}
}, 100);
});

异步函数错误

app.get('/async-await', async (req, res, next) => {
try {
const data = await fetchData();
res.json(data);
} catch (err) {
next(err);
}
});

express-async-errors

自动处理异步错误:

npm install express-async-errors
require('express-async-errors');

app.get('/async', async (req, res) => {
const data = await fetchData();
res.json(data);
});

自定义错误类

class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}

class NotFoundError extends AppError {
constructor(message = '资源未找到') {
super(message, 404);
}
}

class ValidationError extends AppError {
constructor(message = '验证失败') {
super(message, 400);
}
}

class UnauthorizedError extends AppError {
constructor(message = '未授权') {
super(message, 401);
}
}

module.exports = { AppError, NotFoundError, ValidationError, UnauthorizedError };

全局错误处理

const { AppError } = require('./errors');

app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';

if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: err.status,
message: err.message,
stack: err.stack,
error: err
});
} else {
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
} else {
console.error('ERROR:', err);
res.status(500).json({
status: 'error',
message: '服务器内部错误'
});
}
}
});

404 处理

app.use((req, res, next) => {
next(new NotFoundError(`找不到 ${req.originalUrl}`));
});

错误处理最佳实践

错误处理包装函数

const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};

app.get('/users/:id', catchAsync(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new NotFoundError('用户不存在'));
}
res.json(user);
}));

错误处理中间件分离

const errorHandler = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.message || '服务器错误';

res.status(statusCode).json({
success: false,
error: message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};

const notFound = (req, res, next) => {
next(new NotFoundError(`路由 ${req.originalUrl} 不存在`));
};

module.exports = { errorHandler, notFound };

使用示例

const { errorHandler, notFound } = require('./middleware/error');

app.use('/api', apiRoutes);
app.use(notFound);
app.use(errorHandler);

错误日志

const fs = require('fs');
const path = require('path');

const logError = (err) => {
const logPath = path.join(__dirname, '../logs/error.log');
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${err.stack}\n`;

fs.appendFile(logPath, logMessage, (fsErr) => {
if (fsErr) console.error('写入日志失败:', fsErr);
});
};

app.use((err, req, res, next) => {
logError(err);
res.status(err.statusCode || 500).json({
status: 'error',
message: err.message
});
});

常见错误场景

数据库错误

app.post('/users', catchAsync(async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
if (err.code === 11000) {
return next(new ValidationError('邮箱已被注册'));
}
next(err);
}
}));

JWT 错误

const jwt = require('jsonwebtoken');

const verifyToken = (req, res, next) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return next(new UnauthorizedError('请先登录'));
}
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (err) {
if (err.name === 'JsonWebTokenError') {
return next(new UnauthorizedError('Token 无效'));
}
if (err.name === 'TokenExpiredError') {
return next(new UnauthorizedError('Token 已过期'));
}
next(err);
}
};