@infra-x/fwrap
v0.1.1
Published
Type-safe HTTP client with hooks support
Maintainers
Readme
Fetcher
基于 Hooks 的类型安全 HTTP 客户端。
核心特性
- 类型安全的 API 设计,完整的 TypeScript 支持
- 自动 JSON 请求体序列化和响应解析
- 统一的错误处理模型(不抛出异常)
- 灵活的 Hook 系统用于请求拦截和处理
- 内置超时和重试机制
- 支持客户端扩展,方便创建预配置实例
- 零依赖,基于原生 Fetch API
快速开始
import { createClient } from 'fetcher'
const client = createClient({
prefixUrl: 'https://api.example.com',
timeout: 5000,
})
// GET 请求
const { data, error, response } = await client.get('/users')
if (error) {
console.error('请求失败:', error)
} else {
console.log('用户数据:', data)
console.log('响应状态:', response.status)
}
// POST 请求 - 自动 JSON 序列化
const result = await client.post('/users', {
body: { name: 'Alice', email: '[email protected]' },
})客户端扩展
使用 extend() 创建预配置的客户端实例:
// 基础客户端
const client = createClient({
prefixUrl: 'https://api.example.com',
timeout: 10000,
})
// 认证客户端
const authClient = client.extend({
headers: {
Authorization: `Bearer ${token}`,
},
})
// 特定 API 客户端
const usersAPI = authClient.extend({
prefixUrl: 'https://api.example.com/users',
retry: 2,
})响应结构
所有请求返回统一的 FetchResult<T> 结构:
interface FetchResult<T = unknown> {
data: T | null // 自动解析的 JSON 数据,非 JSON 响应为 null
response: Response | null // 原始 Response 对象
error: Error | null // 错误对象,成功时为 null
}自动 JSON 解析规则:
- 响应 Content-Type 为
application/json时自动解析 - 2xx 状态码且响应体为空时,
data为null - 解析失败或非 JSON 响应时,
data为null,可通过response访问原始数据
配置参考
常用选项
| 选项 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| prefixUrl | string \| URL | - | 基础 URL,自动拼接到请求路径前 |
| timeout | number \| false | 10000 | 超时时间(毫秒),false 禁用超时 |
| retry | number \| RetryConfig | 0 | 重试配置,数字表示重试次数 |
| throwHttpErrors | boolean \| function | true | 是否抛出 HTTP 错误(非 2xx) |
| body | object \| string \| ... | - | 请求体,对象自动 JSON 序列化 |
| headers | object | - | 请求头 |
| searchParams | object \| URLSearchParams | - | URL 查询参数 |
| fetch | typeof fetch | globalThis.fetch | 自定义 fetch 实现 |
重试配置
快速配置:
const client = createClient({
retry: 3, // 重试 3 次,使用默认策略
})详细配置对象(RetryConfig):
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| limit | number | - | 最大重试次数 |
| methods | string[] | ['GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'] | 允许重试的 HTTP 方法 |
| statusCodes | number[] | [408, 413, 429, 500, 502, 503, 504] | 触发重试的状态码 |
| delay | function | 指数退避 | 延迟函数 (attemptCount) => number |
| jitter | boolean \| function | false | 是否添加随机抖动 |
| backoffLimit | number | Infinity | 最大退避延迟(毫秒) |
| retryOnTimeout | boolean | true | 超时是否重试 |
示例:
const client = createClient({
retry: {
limit: 3,
methods: ['GET', 'POST'],
statusCodes: [429, 503],
delay: (attemptCount) => 1000 * Math.pow(2, attemptCount),
retryOnTimeout: true,
},
})Hook 系统
Hook 允许在请求生命周期的不同阶段拦截和处理。通过声明式配置实现请求拦截、响应处理、重试控制等功能。
Hook 类型
onRequest- 请求发送前修改 Request 对象onResponse- 响应接收后处理或强制重试onRetry- 重试前修改 Request 或记录日志onError- 错误发生时转换或增强错误信息
声明式配置
在创建客户端时配置 hooks:
import { createClient, RetrySignal } from 'fetcher'
const client = createClient({
prefixUrl: 'https://api.example.com',
onRequest: [
(request) => {
request.headers.set('X-Request-ID', crypto.randomUUID())
return request
},
],
onResponse: [
async (request, options, response) => {
if (response.status === 401) {
await refreshToken()
return RetrySignal // 强制重试
}
},
],
onRetry: [
({ request, error, retryCount }) => {
console.log(`重试第 ${retryCount} 次:`, error.message)
},
],
onError: [
(error, state) => {
console.error('请求失败:', error)
},
],
})使用 extend() 组合 Hooks
通过 extend() 创建具有不同 hook 配置的客户端:
// 基础客户端(带全局日志)
const baseClient = createClient({
prefixUrl: 'https://api.example.com',
onRequest: [
(request) => {
console.log('Request:', request.url)
return request
},
],
})
// 认证客户端 - 继承父 hooks + 添加认证
const authClient = baseClient.extend({
onRequest: [
(request) => {
request.headers.set('Authorization', `Bearer ${getToken()}`)
return request
},
],
onResponse: [
async (request, options, response) => {
if (response.status === 401) {
await refreshToken()
return RetrySignal
}
},
],
})
// authClient 会先执行 baseClient 的日志 hook,再执行认证 hookHook 执行顺序
- hooks 按数组顺序依次执行
extend()创建的客户端先执行父 hooks,再执行自己的 hooks- 多个 hooks 可以协同工作,分别处理不同关注点
错误处理
错误类型
HTTPError- HTTP 错误(非 2xx 状态码)TimeoutError- 请求超时
基础用法
import { HTTPError, TimeoutError } from 'fetcher'
const { data, error } = await client.get('/api/data')
if (error) {
if (error instanceof HTTPError) {
console.error(`HTTP ${error.response.status}:`, error.message)
} else if (error instanceof TimeoutError) {
console.error('请求超时')
} else {
console.error('网络错误:', error)
}
}throwHttpErrors 选项
默认情况下,非 2xx 响应会抛出 HTTPError。可以通过 throwHttpErrors 配置:
// 禁用所有 HTTP 错误抛出
const client = createClient({ throwHttpErrors: false })
// 条件抛出(例如忽略 404)
const client = createClient({
throwHttpErrors: (status) => status !== 404,
})测试
pnpm test # 运行测试
pnpm test:ui # UI 界面
pnpm test:run # 单次运行
pnpm test:coverage # 覆盖率报告