Skip to main content

错误处理

Keq 提供了一套内置的异常类型,用于处理不同场景下的请求错误。这些异常类继承自标准的 JavaScript 错误类,提供了更精确的错误分类和处理能力。

内置异常类型

RequestException

请求异常的基类,用于表示 HTTP 请求过程中发生的错误。

class RequestException extends Error {
  constructor(
    statusCode: number,
    message?: string,
    retry: boolean = true
  )
}

参数说明:

参数类型默认值描述
statusCodenumber-HTTP 状态码
messagestring-错误消息
retrybooleantrue标记该错误是否应该重试

使用示例:

import { request, RequestException } from 'keq'

// 在中间件中抛出请求异常
request.use(async (context, next) => {
  await next()

  if (context.response?.status === 404) {
    throw new RequestException(404, context.response.statusText, false)
  }

  if (context.response?.status >= 500) {
    // 使用响应体内容作为错误消息
    const message = await context.response.text()
    throw new RequestException(context.response.status, message, true)
  }
})

Keq 提供了常见的 HTTP Exception,均继承自 RequestException

  • BadRequestException (400)
  • UnauthorizedException (401)
  • ForbiddenException (403)
  • NotFoundException (404)
  • MethodNotAllowedException (405)
  • NotAcceptableException (406)
  • ProxyAuthenticationRequiredException (407)
  • RequestTimeoutException (408)
  • ConflictException (409)
  • PreconditionFailedException (412)
  • ContentTooLargeException (413)
  • UriTooLongException (414)
  • UnsupportedMediaTypeException (415)
  • ImATeapotException (418)
  • TooManyRequestsException (429)
  • InternalServerErrorException (500)
  • NotImplementedException (501)
  • BadGatewayException (502)
  • ServiceUnavailableException (503)
  • GatewayTimeoutException (504)

AbortException

继承自 DOMException,当请求被主动中止时抛出。

class AbortException extends DOMException {
  constructor(message?: string)
}

参数说明:

参数类型默认值描述
messagestring-错误消息

使用示例:

import { request, AbortException, KeqMiddleware } from 'keq'

// 创建一个 3 秒后自动中止请求的中间件
function autoAbortMiddleware(): KeqMiddleware {
  return async (context, next) => {
    // 3 秒后中止请求
    const timer = setTimeout(() => {
      context.request.abort('请求执行时间过长')
    }, 3000)

    try {
      await next()
    } finally {
      clearTimeout(timer)
    }
  }
}

try {
  await request
    .get('/cats')
    .use(autoAbortMiddleware())
} catch (err) {
  if (err instanceof AbortException) {
    console.error('请求被中止:', err.message)
  }
}

TimeoutException

继承自 AbortException,当请求超时时抛出。通常由 .timeout() 方法触发。

class TimeoutException extends AbortException {
  constructor(message?: string)
}

参数说明:

参数类型默认值描述
messagestring-错误消息

使用示例:

import { request, TimeoutException } from 'keq'

try {
  await request
    .get('/cats')
    .timeout(3000)
} catch (err) {
  if (err instanceof TimeoutException) {
    console.error('请求超时:', err.message)
  }
}

TypeException

参数类型错误异常,类似于标准的 TypeError,用于标识参数传递错误。

class TypeException extends TypeError {}
info

在 TypeScript 项目中,通常不会遇到此异常,因为 TypeScript 编译器会在编译时捕获类型错误。此异常主要用于运行时类型检查或纯 JavaScript 项目。

使用示例:

import { request, TypeException } from 'keq'

try {
  // 错误:attach 方法期望第二个参数是 File、Blob 或 Buffer
  await request
    .post('/cats')
    .attach('cat.json', 123) // 传入了 number 类型
} catch (err) {
  if (err instanceof TypeException) {
    console.error('参数类型错误:', err.message)
    // 输出: 参数类型错误: Invalid file type for .attach()
  }
}

错误处理最佳实践

使用 instanceof 判断错误类型

import { request, RequestException, TimeoutException } from 'keq'

try {
  await request.get('/cats').timeout(5000)
} catch (err) {
  if (err instanceof TimeoutException) {
    console.error('请求超时,请稍后重试')
  } else if (err instanceof RequestException) {
    console.error(`请求失败: ${err.statusCode} ${err.message}`)
  } else {
    console.error('未知错误:', err)
  }
}

在中间件中统一处理错误

import { request, KeqMiddleware, RequestException } from 'keq'

// 自定义异常类
export class UnauthorizedException extends RequestException {
  constructor(message?: string) {
    super(401, message || 'Unauthorized', false)
    this.name = 'UnauthorizedException'
  }
}

export class ForbiddenException extends RequestException {
  constructor(message?: string) {
    super(403, message || 'Forbidden', false)
    this.name = 'ForbiddenException'
  }
}

export class NotFoundException extends RequestException {
  constructor(message?: string) {
    super(404, message || 'Not Found', false)
    this.name = 'NotFoundException'
  }
}

function errorHandler(): KeqMiddleware {
  return async (context, next) => {
    await next()

    if (context.response) {
      const { status, statusText } = context.response

      // 根据不同的状态码抛出特定的异常
      if (status === 401) {
        throw new UnauthorizedException(statusText)
      } else if (status === 403) {
        throw new ForbiddenException(statusText)
      } else if (status === 404) {
        throw new NotFoundException(statusText)
      } else if (status >= 400 && status < 500) {
        // 其他客户端错误不重试
        throw new RequestException(status, statusText, false)
      } else if (status >= 500) {
        // 服务器错误可重试
        const message = await context.response.text()
        throw new RequestException(status, message, true)
      }
    }
  }
}

request.use(errorHandler())

// 使用时可以精确捕获不同类型的错误
try {
  await request.get('/cats')
} catch (err) {
  if (err instanceof UnauthorizedException) {
    console.error('未授权,请先登录')
    // 跳转到登录页
  } else if (err instanceof ForbiddenException) {
    console.error('没有权限访问该资源')
  } else if (err instanceof NotFoundException) {
    console.error('资源不存在')
  } else if (err instanceof RequestException) {
    console.error(`请求失败: ${err.statusCode} ${err.message}`)
  }
}