# afetch
[English](./README.md) | **中文**
轻量、类型安全、插件化的 Fetch API 封装库。
[](https://www.npmjs.com/package/@ahriknow/afetch)
[](./LICENSE)
[](https://codecov.io/gh/ahriknow/afetch)
[](https://www.typescriptlang.org/)
---
## 特性
- 🚀 **轻量** — 零依赖,极小的打包体积
- 🔒 **类型安全** — 完整的 TypeScript 支持,严格类型推断
- 🧩 **插件系统** — 通过 `beforeRequest`、`afterResponse`、`onError` 钩子扩展功能
- 🔁 **重试插件** — 自动重试,支持指数退避、状态码匹配、自定义 hook
- 📡 **事件总线插件** — 通过事件监听请求生命周期
- 📨 **SSE 支持** — Server-Sent Events 客户端,配备插件系统和自动重连 (`@ahriknow/afetch/sse`)
- 🔌 **WebSocket 支持** — WebSocket 客户端,配备插件系统、自动重连和请求同步 (`@ahriknow/afetch/ws`)
- ⏱️ **超时** — 请求超时自动中断
- ❌ **取消** — 支持 AbortController + Task API 细粒度控制
- 📊 **进度** — 上传和下载进度追踪
- 🏗️ **实例** — 创建预配置的请求实例
- 🔧 **转换** — 请求和响应数据转换
- 🌐 **通用** — 支持浏览器(Chrome 42+、Firefox 39+、Safari 10.1+)和 Node.js 18+
## 安装
```bash
npm install @ahriknow/afetch
```
## 快速开始
```typescript
import { afetch } from '@ahriknow/afetch';
// GET 请求
const { data } = await afetch.get('/api/users');
// POST 请求
const { data: user } = await afetch.post('/api/users', {
name: '张三',
email: 'zhangsan@example.com',
});
// 带配置项
const { data: item } = await afetch.get- ('/api/items/1', {
headers: { Authorization: 'Bearer token' },
timeout: 5000,
params: { fields: 'name,email' },
});
```
## 创建实例
```typescript
import { createInstance } from '@ahriknow/afetch';
const api = createInstance({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: { 'Content-Type': 'application/json' },
});
const { data: users } = await api.get('/users');
const { data: user } = await api.post('/users', { name: '张三' });
```
### 重试插件
```typescript
import { createRetryPlugin } from '@ahriknow/afetch';
const api = createInstance({ baseURL: 'https://api.example.com' });
// 插件级默认配置——所有请求自动继承
api.use(createRetryPlugin({
maxRetries: 3,
delay: 1000,
retryOn: [500, 502, 503, 504],
}));
// 使用插件默认配置(遇到 5xx 错误自动重试 3 次)
await api.get('/api/data');
// 单个请求覆盖插件配置
await api.get('/api/critical', {
meta: { retry: { maxRetries: 5, delay: 2000 } },
});
// 指数退避
api.use(createRetryPlugin({
maxRetries: 5,
delay: (attempt) => Math.pow(2, attempt) * 1000,
}));
// 高级用法:401 时自动刷新 token
api.use(createRetryPlugin({
maxRetries: 3,
delay: 1000,
retryOn: [
500,
{
hook: async (error) => error.status === 401,
retryDelay: 0,
call: async () => {
const token = await refreshToken();
api.defaults.headers!['Authorization'] = `Bearer ${token}`;
},
},
],
}));
// 请求级自定义条件函数
await api.get('/api/data', {
meta: {
retry: {
condition: (attempt, error) => error.status === 503 && attempt < 2,
},
},
});
```
### 事件总线插件
```typescript
import { createEventBusPlugin } from '@ahriknow/afetch';
const api = createInstance({ baseURL: 'https://api.example.com' });
const eventBus = createEventBusPlugin();
api.use(eventBus);
// 监听请求事件
const unsub = eventBus.on('request', ({ config }) => {
console.log(`→ ${config.method} ${config.url}`);
});
// 监听响应事件
eventBus.on('response', ({ config, response }) => {
console.log(`← ${response.status} ${config.url}`);
});
// 监听错误事件
eventBus.on('error', ({ config, error }) => {
console.error(`✗ ${error.code} ${config.url}`);
});
// 取消监听
unsub();
// 移除某个事件的所有监听器
eventBus.off('response');
```
### 队列插件
自动控制并发请求执行。只需安装插件,所有请求自动受并发限制:
```typescript
import { createQueuePlugin } from '@ahriknow/afetch';
const queue = createQueuePlugin({ maxConcurrent: 3 });
api.use(queue);
// 所有请求自动排队,最多 3 个同时执行
const results = await Promise.all(
urls.map(url => api.get(url))
);
console.log('执行中:', queue.pending);
console.log('排队中:', queue.queued);
queue.clear(); // 拒绝所有等待中的请求
```
### 缓存插件
自动缓存 GET 响应:
```typescript
import { createCachePlugin } from '@ahriknow/afetch';
api.use(createCachePlugin({
maxAge: 30_000,
maxSize: 50,
// 可选:按请求决定是否缓存
shouldCache: (config, response) => {
if (response.status >= 400) return false;
if (config.url.startsWith('/users')) return { maxAge: 60_000 };
return true; // 使用默认 maxAge
},
}));
// 第一次请求——网络请求
const users1 = await api.get('/users');
// 第二次请求——从缓存返回(无网络请求)
const users2 = await api.get('/users');
// POST 请求不会被缓存
await api.post('/users', { name: '张三' });
```
### 编写自定义插件
```typescript
import type { AFetchPlugin } from '@ahriknow/afetch';
const loggerPlugin: AFetchPlugin = {
name: 'logger',
install(api) {
api.addHook('beforeRequest', ({ config }) => {
console.log(`[REQ] ${config.method} ${config.baseURL}${config.url}`);
});
api.addHook('afterResponse', ({ response }) => {
console.log(`[RES] ${response.status} ${response.statusText}`);
});
api.addHook('onError', ({ error }) => {
console.error(`[ERR] ${error.code}: ${error.message}`);
});
},
};
api.use(loggerPlugin);
```
### 插件生命周期钩子
| 钩子 | 触发时机 | 返回值 |
|------|---------|--------|
| `beforeRequest` | 请求发送前 | `AResponse`(缓存命中)或 `void` |
| `afterResponse` | 收到响应后 | `AResponse`(替换响应)或 `void` |
| `onError` | 请求出错时 | `AResponse`(重试/替换)或 `void`(传播错误) |
插件按实例安装——同一插件重复调用 `use()` 不会重复安装。
## 文档
| 主题 | 说明 |
|------|------|
| [错误处理](./docs/zh-CN/error-handling.md) | AFetchError、错误类型和错误处理模式 |
| [取消请求](./docs/zh-CN/cancellation.md) | Task API 和 AbortController |
| [数据转换](./docs/zh-CN/transforms.md) | 请求和响应数据转换 |
| [TypeScript 支持](./docs/zh-CN/typescript.md) | 类型定义和泛型支持 |
| [配置](./docs/zh-CN/configuration.md) | 实例和单次请求配置选项 |
| [API 参考](./docs/zh-CN/api-reference.md) | 完整 API 参考 |
| [SSE(Server-Sent Events)](./docs/zh-CN/sse.md) | SSE 客户端,配备插件系统和自动重连 |
| [WebSocket](./docs/zh-CN/ws.md) | WebSocket 客户端,配备插件系统、自动重连和请求同步 |
## 许可证
[MIT](./LICENSE)