示例说明
TypeScript 给错误处理带来两个杀手锏:判别字段与穷尽检查。AppError 的 kind 是字面量联合,statusFor 的 switch 覆盖全部分支且没有 default——新增错误种类时漏写分支会直接编译报错。中间件先 instanceof 收窄类型,业务错误按 kind 映射状态码,非预期错误记日志后回笼统响应不泄漏内部细节。
技术要点
- kind 判别联合支撑穷尽 switch
- wrap 桥接 Promise 拒绝与 next
- instanceOf 收窄未知错误类型
代码解读
wrap 的高阶函数把异步 handler 的异常转交错误链;示例路由对 id 为 0 抛出 not-found 错误。错误中间件里非 AppError 实例走兜底分支只记堆栈,AppError 则映射状态码并回显 kind,客户端可按类别做差异化提示。
使用建议
错误消息要区分对内对外:对外文案克制、对内日志详尽。新增 kind 时利用编译器穷尽提示补全映射。生产环境给 5xx 加告警钩子。
代码示例
import express, { NextFunction, Request, Response } from 'express';
type ErrorKind = 'validation' | 'not-found' | 'conflict' | 'internal';
class AppError extends Error {
constructor(
readonly kind: ErrorKind,
message: string,
readonly status = 500,
) {
super(message);
}
}
const statusFor = (kind: ErrorKind): number => {
switch (kind) {
case 'validation': return 400;
case 'not-found': return 404;
case 'conflict': return 409;
case 'internal': return 500;
}
};
const wrap = (fn: (req: Request, res: Response) => Promise<unknown>) =>
(req: Request, res: Response, next: NextFunction) => {
fn(req, res).catch(next);
};
const app = express();
app.use(express.json());
app.get('/items/:id', wrap(async (req, res) => {
if (req.params.id === '0') {
throw new AppError('not-found', 'item 0 does not exist');
}
res.json({ id: req.params.id });
}));
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
if (!(err instanceof AppError)) {
console.error('unexpected:', err);
res.status(500).json({ error: 'internal error' });
return;
}
const status = statusFor(err.kind);
if (status >= 500) console.error('server error:', err.message);
res.status(status).json({ error: err.message, kind: err.kind });
});
app.listen(3000);