错误抛出、错误捕捉机制
约 810 字大约 3 分钟
2025-03-30
Koa 提供了多种方式来处理异步和同步错误,下面详细介绍错误抛出和捕捉的机制。
错误抛出
1. 使用 ctx.throw()
(推荐)
Koa 上下文提供了 throw
方法专门用于抛出 HTTP 错误:
app.use(async (ctx, next) => {
// 基本用法
if (!ctx.query.token) {
ctx.throw(401, "未提供认证令牌"); // status, message
}
// 带属性
if (!ctx.user) {
ctx.throw(403, "禁止访问", {
code: "FORBIDDEN",
redirect: "/login",
});
}
});
2. 直接抛出 Error 对象
app.use(async (ctx, next) => {
if (!ctx.query.id) {
throw new Error("id参数缺失"); // 会被Koa捕获
}
});
3. 自定义错误类型
class NotFoundError extends Error {
constructor(message) {
super(message);
this.status = 404;
this.expose = true; // 允许将错误信息返回给客户端
}
}
app.use(async (ctx, next) => {
const user = await getUser(ctx.params.id);
if (!user) {
throw new NotFoundError("用户不存在");
}
});
错误捕捉
1. 中间件 try/catch
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.expose ? err.message : "Internal Server Error",
code: err.code,
};
ctx.app.emit("error", err, ctx); // 触发应用级错误事件
}
});
2. 应用级错误事件
app.on("error", (err, ctx) => {
// 记录错误日志
console.error("Server error:", err, ctx);
// 生产环境可以发送错误通知
if (process.env.NODE_ENV === "production") {
sendErrorToMonitoringService(err);
}
});
3. 特定错误的处理
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (err instanceof ValidationError) {
ctx.status = 400;
ctx.body = { errors: err.errors };
return;
}
if (err.code === "NOT_FOUND") {
ctx.status = 404;
ctx.body = { error: "资源不存在" };
return;
}
// 其他未处理错误
ctx.status = 500;
ctx.body = { error: "服务器错误" };
ctx.app.emit("error", err, ctx);
}
});
最佳实践
错误分类处理:
- 业务错误 (4xx):暴露给客户端
- 系统错误 (5xx):只记录不暴露详情
错误信息控制:
ctx.throw(400, "Invalid input", { expose: true, // 是否向客户端暴露错误详情 code: "INVALID_INPUT", details: { /* 额外错误详情 */ }, });
统一错误格式:
// 错误响应统一格式 { "error": "错误描述", "code": "错误代码", "details": { /* 可选详情 */ }, "requestId": "请求唯一ID" }
生产环境安全:
app.use(async (ctx, next) => { try { await next(); } catch (err) { ctx.status = err.status || 500; ctx.body = { error: process.env.NODE_ENV === "development" || err.expose ? err.message : "Internal Server Error", }; } });
完整示例
const Koa = require("koa");
const app = new Koa();
// 自定义错误类型
class ApiError extends Error {
constructor(status, code, message, details = {}) {
super(message);
this.status = status;
this.code = code;
this.details = details;
this.expose = true;
}
}
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.expose ? err.message : "Internal Server Error",
code: err.code || "INTERNAL_ERROR",
details: err.details,
requestId: ctx.state.requestId,
};
// 触发应用级错误事件
if (!err.expose || err.status >= 500) {
ctx.app.emit("error", err, ctx);
}
}
});
// 应用级错误日志
app.on("error", (err, ctx) => {
console.error(`[${new Date().toISOString()}]`, {
requestId: ctx.state.requestId,
method: ctx.method,
path: ctx.path,
status: ctx.status,
error: {
message: err.message,
stack: err.stack,
details: err.details,
},
});
});
// 业务路由
app.use(async (ctx) => {
if (!ctx.query.userId) {
throw new ApiError(400, "MISSING_PARAM", "缺少userId参数");
}
const user = await getUser(ctx.query.userId);
if (!user) {
throw new ApiError(404, "USER_NOT_FOUND", "用户不存在", {
userId: ctx.query.userId,
});
}
ctx.body = { data: user };
});
function getUser(id) {
if (id === "123") {
return { id: "123", name: "测试用户" };
}
return null;
}
app.listen(3000);
注意事项
- 异步错误:确保所有异步操作都在 try/catch 中或有 catch 处理
- 错误冒泡:未被捕获的错误会导致进程退出,务必全局捕获
- 敏感信息:生产环境不要暴露堆栈等敏感信息
- HTTP 状态码:遵循 RESTful 规范使用正确的状态码
Koa 的错误处理机制非常灵活,合理使用可以构建健壮的应用程序。
更新日志
2025/8/24 08:17
查看所有更新日志
e7112
-1于