错误处理中间件:app.onError((err, c) => ...) 如何捕获异步异常?
捕获 Promise rejection、同步抛出、路由未匹配
Hono 的 app.onError() 是全局错误处理的中心枢纽。它能够捕获在请求处理生命周期中发生的各种异常,包括同步抛出的错误、异步 Promise 拒绝(rejection),以及特殊的“未找到路由”情况。理解其工作原理对于构建健壮的 Web 应用至关重要。
一、onError 的基本用法
ts
app.onError((err, c) => {
console.error('Global error:', err)
return c.json({ error: 'Internal Server Error' }, 500)
})该回调在任何中间件或路由处理器中发生未捕获异常时被调用。你可以在其中记录错误、发送告警、返回统一错误响应。
二、onError 能捕获哪些类型的错误?
1. 同步抛出的错误(Synchronous Throw)
ts
app.get('/sync-error', () => {
throw new Error('Sync error')
})- Hono 在调用处理器时会使用
try...catch包裹; - 同步
throw会被立即捕获,并触发onError; - ✅ 能被捕获
2. 异步 Promise 拒绝(Asynchronous Rejection)
ts
app.get('/async-error', async () => {
const res = await fetch('https://httpbin.org/status/500')
if (!res.ok) {
throw new Error('Fetch failed') // 抛出一个 rejected promise
}
})async函数返回一个Promise;- 当
Promise被拒绝(rejected),Hono 会将其视为错误; - 内部机制自动将
Promise的 rejection 转换为错误并传递给onError; - ✅ 能被捕获
3. 未显式处理的 Promise 拒绝(Uncaught Promise Rejection)
ts
app.get('/unhandled-rejection', () => {
fetch('https://invalid-url/api').then(res => {
// 忘记处理 .catch()
return res.json()
})
return 'OK' // 请求已完成,但后台 Promise 可能失败
})- 这种情况 不会触发
onError❌; - 因为
fetch().then()的错误发生在事件循环的后续阶段,与当前请求的执行流已分离; onError只能捕获直接关联到当前请求处理链的错误;- 这类错误需通过平台级事件(如
process.on('unhandledRejection'))处理。
4. 路由未匹配(404 Not Found)
ts
app.get('/', (c) => c.text('Home'))
// 请求 /unknown-path- 默认情况下,Hono 在找不到匹配路由时会返回
404响应; - 默认不触发
onError❌; - 但你可以通过自定义
NotFound Handler来改变行为:
ts
app.notFound((c) => {
const err = new Error('Route not found')
// 主动触发 onError
return app.getErrorHandler()(err, c)
})
// 或者更简单地,在 notFound 中直接返回错误响应
app.notFound((c) => c.json({ error: 'Not Found' }, 404))如果你想让 404 也走 onError 流程,可以这样做:
ts
const customErrorHandler = (err, c) => {
if (err.message === 'Not Found') {
return c.json({ error: 'Route not found' }, 404)
}
return c.json({ error: 'Internal Server Error' }, 500)
}
app.onError(customErrorHandler)
app.notFound((c) => {
return customErrorHandler(new Error('Not Found'), c)
})三、Hono 内部如何捕获错误?
Hono 的核心处理逻辑大致如下:
ts
async function handleRequest(request, env, executionCtx) {
try {
// 执行中间件链和处理器
const response = await executeMiddlewaresAndHandler()
// 如果没有返回 Response,创建一个
if (!response) {
return new Response('OK')
}
return response
} catch (err) {
// 捕获同步 throw 和 async 函数的 rejection
return app.onError(err, context)
}
}关键点:
executeMiddlewaresAndHandler()返回一个Promise;- 整个执行过程被
try...catch包裹; - 无论是同步
throw还是async函数的reject,都会进入catch分支; - 然后调用用户定义的
onError回调。
四、最佳实践:编写可靠的错误处理
始终使用
onError定义全局兜底tsapp.onError((err, c) => { // 生产环境避免泄露堆栈 const message = process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message return c.json({ error: message }, 500) })主动处理异步操作的错误
tsapp.get('/safe-fetch', async (c) => { try { const res = await fetch('https://api.example.com/data') if (!res.ok) throw new Error('API error') const data = await res.json() return c.json(data) } catch (err) { // 可以在这里处理,或让 onError 捕获 return c.json({ error: err.message }, 500) } })将 404 视为业务错误(可选)
tsapp.notFound((c) => { return app.onError(new Error('Not Found'), c) })记录错误日志
tsapp.onError((err, c) => { console.error(`[${c.req.method}] ${c.req.url} - ${err.stack}`) return c.json({ error: 'Something went wrong' }, 500) })
五、结论
app.onError() 能够有效捕获:
- ✅ 同步
throw - ✅
async函数中的throw(即 Promise rejection)
但无法捕获:
- ❌ 脱离请求上下文的未处理 Promise 拒绝
- ❌ 默认的 404 路由未匹配(除非主动调用
onError)
通过理解这些边界,你可以设计出既能防御意外错误,又能清晰处理业务异常的健壮应用。onError 不仅是安全网,更是实现统一错误响应、监控和告警的基础。