Skip to content

错误处理中间件: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 回调。

四、最佳实践:编写可靠的错误处理

  1. 始终使用 onError 定义全局兜底

    ts
    app.onError((err, c) => {
      // 生产环境避免泄露堆栈
      const message = process.env.NODE_ENV === 'production' 
        ? 'Internal Server Error' 
        : err.message
      return c.json({ error: message }, 500)
    })
  2. 主动处理异步操作的错误

    ts
    app.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)
      }
    })
  3. 将 404 视为业务错误(可选)

    ts
    app.notFound((c) => {
      return app.onError(new Error('Not Found'), c)
    })
  4. 记录错误日志

    ts
    app.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 不仅是安全网,更是实现统一错误响应、监控和告警的基础。