Http client for NodeJs / browser and WeChat, based on fetch()
npm install akita简洁易用的多平台 HTTP 客户端,支持 Node.js、浏览器和微信小程序,基于 Fetch API 实现。
- Promise 接口风格 - 现代 async/await 语法
- 自动 JSON 解析 - 直接获取解析后的数据 users = await client.get('/api/users')
- 多种响应格式 - 支持 JSON、文本、Buffer、Blob、Stream、JsonStream
- 自动查询参数序列化 - 使用 qs 库处理复杂对象和数组
- 自动表单数据转换 - 智能识别并转换为 FormData
- 自动文件上传 - 检测到文件/Blob/Uint8Array 时自动处理
- 强大的钩子系统 - 4 个生命周期钩子 + 进度钩子
- 自动错误识别 - 检测服务器返回的 {error:'错误信息',code:'错误码'}
- 多客户端实例 - 支持创建独立的客户端实例
- 客户端配置共享 - 通过 resolve() 在多个模块间共享配置
- 自动 URL 拼装 - 智能处理 apiRoot 和路径的拼接
``bash`
npm install akita或
yarn add akita
`typescript
import akita from 'akita';
// GET 请求
const users = await akita.get('/api/users');
console.log(users);
// POST 请求
const user = await akita.post('/api/users', {
body: { name: 'John', age: 30 }
});
// PUT 请求
await akita.put('/api/users/1', {
body: { name: 'John Doe', email: 'john.doe@example.com' }
});
// PATCH 请求
await akita.patch('/api/users/1', {
body: { name: 'Updated Name' }
});
// DELETE 请求
await akita.delete('/api/users/1');
`
`typescript
const client = akita.create({
apiRoot: 'https://api.example.com/v1'
});
// 所有请求自动使用 apiRoot
const users = await client.get('/users');
// 发送到: https://api.example.com/v1/users
const user = await client.get('users');
// 发送到: https://api.example.com/v1/users (自动处理斜杠)
`
`typescript
// 简单查询参数
const users = await akita.get('/users', {
query: { page: 1, limit: 10 }
});
// -> /users?page=1&limit=10
// 嵌套对象
const users = await akita.get('/users', {
query: { filter: { name: 'John', age: 30 } }
});
// -> /users?filter[name]=John&filter[age]=30
// 数组
const users = await akita.get('/users', {
query: { ids: [1, 2, 3] }
});
// -> /users?ids[0]=1&ids[1]=2&ids[2]=3
// URL 中已有参数
const users = await akita.get('/users?active=true', {
query: { page: 1 }
});
// -> /users?active=true&page=1
`
`typescript`
const data = await akita.get('/api/data', {
headers: {
'Authorization': 'Bearer token123',
'Accept': 'application/json',
'X-Custom-Header': 'custom-value'
}
});
`typescript
const client = akita.create({
init: {
headers: {
'Authorization': 'Bearer token',
'Accept': 'application/json'
}
}
});
// 所有请求自动带上这些 headers
const users = await client.get('/users');
`
`typescript
// 使用 AbortSignal (推荐)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const data = await client.get('/api/slow', {
signal: controller.signal
});
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request timeout');
}
} finally {
clearTimeout(timeoutId);
}
// 或使用 timeout 参数 (Node.js)
const data = await client.get('/api/data', {
timeout: 5000
});
`
`typescript
// JSON 数据 (默认)
const users = await akita.get('/api/users');
console.log(users); // [{ id: 1, name: 'John' }, ...]
// 原始文本
const html = await akita.get('/page').text();
console.log(html); // ...
// Buffer 数据 (适合二进制文件)
const buffer = await akita.get('/image.png').buffer();
import fs from 'fs';
fs.writeFileSync('image.png', buffer);
// Blob 数据 (浏览器端)
const blob = await akita.get('/file.pdf').blob();
const url = URL.createObjectURL(blob);
window.open(url);
// 流数据
const stream = await akita.get('/large-file.zip').stream();
stream.pipe(fs.createWriteStream('file.zip'));
// 响应对象
const response = await akita.get('/api/users').response();
console.log(response.status); // 200
console.log(response.statusText); // 'OK'
console.log(response.ok); // true
console.log(response.headers.get('Content-Type'));
// 获取元数据
const status = await akita.get('/users').status();
const ok = await akita.get('/users').ok();
const headers = await akita.get('/users').headers();
const size = await akita.get('/users').size();
`
`typescript
// 提取嵌套的 data 字段
const users = await akita.get('/api/users', {}, (json) => json.data);
// 转换日期格式
const user = await akita.get('/api/user/1', {}, (json) => ({
...json,
createdAt: new Date(json.createdAt),
updatedAt: new Date(json.updatedAt)
}));
// 提取并重命名字段
const data = await akita.get('/api/data', {}, (json) => ({
userId: json.user_id,
userName: json.user_name,
userAge: json.user_age
}));
// 数组转换
const userIds = await akita.get('/api/users', {}, (json) =>
json.map((user: any) => user.id)
);
// 数据验证
const validated = await akita.get('/api/user/1', {}, (json) => {
if (!json.name) {
throw new Error('Invalid user: missing name');
}
return json;
});
`
`typescript`
const user = await akita.post('/users', {
body: { name: 'John', age: 30, email: 'john@example.com' }
});
// Content-Type: application/json
// Body: {"name":"John","age":30,"email":"john@example.com"}
`typescript`
const result = await akita.post('/login', {
body: { username: 'admin', password: '123456' },
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
// Content-Type: application/x-www-form-urlencoded
// Body: username=admin&password=123456
`typescript
// 浏览器端
const fileInput = document.getElementById('file');
const result = await akita.post('/upload', {
body: {
file: fileInput.files[0],
name: 'test.txt'
}
});
// Node.js 端
import fs from 'fs';
const result = await akita.post('/upload', {
body: {
file: fs.createReadStream('./file.txt'),
description: 'Upload description'
}
});
// Buffer 上传
const buffer = Buffer.from('hello world');
// @ts-ignore
buffer.name = 'test.txt';
const result = await akita.post('/upload', {
body: {
file: buffer,
filename: 'test.txt'
}
});
// 多文件上传
const result = await akita.post('/upload', {
body: {
file1: fs.createReadStream('./file1.txt'),
file2: fs.createReadStream('./file2.txt'),
metadata: { batchId: '123' }
}
});
`
服务器返回格式(每行一个 JSON 对象):
``
{"type":"ADDED","object":{"id":1,"name":"iPhone"}}
{"type":"MODIFIED","object":{"id":2,"name":"iMac"}}
`typescript`
const stream = await akita.get('/api/events').jsonStream();
let event = await stream.read();
while (event) {
console.log('Event:', event.type, event.object);
event = await stream.read();
}
`typescript
const stream = await akita.get('/api/events').jsonStream();
stream.on('data', (event) => {
console.log('Received event:', event.type, event.object);
});
stream.on('error', (error) => {
console.error('Stream error:', error.message);
});
stream.on('close', () => {
console.log('Stream closed');
});
`
`typescript
const stream = await akita.get('/api/events').jsonStream();
// 10 秒后关闭
setTimeout(() => {
stream.close();
}, 10000);
let event = await stream.read();
while (event) {
console.log(event);
event = await stream.read();
}
// 10 秒后,event 会是 undefined
`
Akita 提供了 5 个钩子来监听请求生命周期:
| 钩子 | 触发时机 | 用途 |
|------|---------|------|
| onEncode | Body 编码为 JSON/FormData 前 | 添加时间戳、验证数据 |onRequest
| | 请求发送前 | 添加认证 token、请求 ID |onResponse
| | 接收到响应后 | 日志记录、处理特定状态码 |onDecode
| | 响应体解析为 JSON 后 | 数据转换、字段重命名 |onProgress
| | 请求进度更新 | 显示进度条 |
`typescriptBearer ${token}
const client = akita.create({
onRequest: async (request) => {
// 自动添加认证 token
const token = await getAuthToken();
request.init.headers['Authorization'] = ;
// 添加请求 ID
request.init.headers['X-Request-ID'] = generateUUID();
}
});
const data = await client.get('/api/data');
// 请求会自动带上 Authorization 和 X-Request-ID 头
`
`typescript${request.init.method} ${request.url} -> ${request.res?.status}
const client = akita.create({
onResponse: async (request) => {
// 记录请求日志
console.log();
// 处理 401 错误(Token 过期)
if (request.res?.status === 401) {
console.log('Token expired, redirecting to login');
window.location.href = '/login';
}
// 处理特定错误状态码
if (request.res?.status >= 500) {
console.error('Server error:', request.res?.statusText);
}
}
});
`
`typescript
interface User {
id: number;
name: string;
createdAt: Date;
updatedAt: Date;
}
const client = akita.create({
onDecode: async (request) => {
// 转换日期字段
if (request.value?.createdAt) {
request.value.createdAt = new Date(request.value.createdAt);
}
if (request.value?.updatedAt) {
request.value.updatedAt = new Date(request.value.updatedAt);
}
// 提取嵌套的 data 字段
if (request.value?.data) {
request.value = request.value.data;
}
// 统一数据格式
if (request.value?.user) {
request.value.userId = request.value.user.id;
request.value.userName = request.value.user.name;
delete request.value.user;
}
}
});
const users = await client.get
// users 中的 createdAt/updatedAt 已经是 Date 对象
`
`typescript
const client = akita.create({
onEncode: async (request) => {
// 添加时间戳
if (request.init.body && typeof request.init.body === 'object') {
request.init.body.timestamp = Date.now();
}
// 验证数据
if (request.init.body?.email && !isValidEmail(request.init.body.email)) {
throw new Error('Invalid email format');
}
// 添加设备信息
if (request.init.body) {
request.init.body.device = {
userAgent: navigator.userAgent,
platform: navigator.platform
};
}
}
});
`
`typescriptOverall progress: ${percentage}%
const client = akita.create({
onProgress: (progress) => {
const percentage = (progress * 100).toFixed(1);
console.log();
// 更新 UI 进度条
if (typeof window !== 'undefined') {
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = ${percentage}%;
}
}
}
});
// 并发多个请求
Promise.all([
client.get('/api/users'),
client.get('/api/products'),
client.get('/api/orders')
]);
// 会触发多次进度更新: 33.3% -> 66.7% -> 100.0%
`
`typescriptBearer ${token}
const client = akita.create({
onRequest: [
async (request) => {
// 第一个钩子:添加 token
const token = await getToken();
request.init.headers['Authorization'] = ;`
},
(request) => {
// 第二个钩子:添加请求 ID
request.init.headers['X-Request-ID'] = generateId();
},
(request) => {
// 第三个钩子:记录日志
console.log('Request:', request.init.method, request.url);
}
]
});
`typescript
const client = akita.create();
// 添加钩子
const logHook = (request: any) => {
console.log('Request to:', request.url);
};
client.on('request', logHook);
// 移除钩子
client.off('request', logHook);
// 链式调用
client
.on('request', (req) => console.log('Sending:', req.url))
.on('response', (req) => console.log('Received:', req.res?.status))
.on('progress', (p) => console.log('Progress:', (p * 100).toFixed(0) + '%'));
`
`typescript
// 基础客户端
const baseClient = akita.create({
apiRoot: 'https://api.example.com',
init: {
headers: { 'Accept': 'application/json' }
}
});
// 创建带认证的客户端
const authClient = baseClient.create({
init: {
headers: { 'Authorization': 'Bearer token' }
}
});
// 两个客户端配置独立
console.log(baseClient.options.init.headers);
// { Accept: 'application/json' }
console.log(authClient.options.init.headers);
// { Accept: 'application/json', Authorization: 'Bearer token' }
`
`typescript
// 在 user.js 中
import akita from 'akita';
const userClient = akita.resolve('api');
userClient.setOptions({
apiRoot: 'https://api.example.com',
init: { headers: { 'Authorization': 'Bearer token' } }
});
export { userClient };
// 在 product.js 中
import akita from 'akita';
const productClient = akita.resolve('api');
// 获取的是同一个实例,已经配置好了
export { productClient };
// 在 order.js 中
import akita from 'akita';
const orderClient = akita.resolve('api');
// 还是同一个实例,配置完全共享
export { orderClient };
// 所有文件使用相同的配置和 token
`
`typescript
const client = akita.create({
apiRoot: 'https://api.example.com',
init: { headers: { 'Accept': 'application/json' } }
});
// 动态添加 token(登录后)
client.setOptions({
init: { headers: { 'Authorization': 'Bearer new-token' } }
});
// Headers 会合并
console.log(client.options.init.headers);
// { Accept: 'application/json', Authorization: 'Bearer new-token' }
`
Akita 提供了完整的错误类型系统,支持 4 种错误类型:
| 错误类型 | 说明 | 示例代码 |
|---------|------|---------|
| Network | 网络层错误(DNS 失败、超时、连接拒绝等) | NETWORK_TIMEOUT, NETWORK_DNS_FAILED, NETWORK_CONNECTION_REFUSED |HTTP_404
| HTTP | HTTP 状态码错误(4xx/5xx) | , HTTP_500, HTTP_401 |PARSE_JSON_ERROR
| Parse | JSON/text 解析失败 | , PARSE_TEXT_ERROR |error
| Server | 应用层错误(响应中包含 字段) | SERVER_ERROR, USER_NOT_FOUND(自定义代码) |
所有错误都是 AkitaError 实例,包含以下属性:
`typescript`
class AkitaError extends Error {
name: 'AkitaError'; // 固定值 'AkitaError'
type: 'network' | 'http' | 'parse' | 'server'; // 错误类型
code: string; // 错误代码
networkType?: NetworkErrorType; // 网络错误子类型
status?: number; // HTTP 状态码
statusText?: string; // HTTP 状态文本
url?: string; // 请求 URL
method?: string; // HTTP 方法
cause?: Error; // 原始错误(包裹)
timestamp?: number; // 错误时间戳
}
Akita 导出了 5 个类型守卫函数,用于安全地判断错误类型:
`typescript
import akita from 'akita';
import {
isAkitaError,
isNetworkError,
isHTTPError,
isParseError,
isServerError
} from 'akita';
try {
const data = await akita.get('/api/data');
} catch (error) {
// 1. 判断是否为 AkitaError
if (isAkitaError(error)) {
console.log('Akita error:', error.code);
}
// 2. 判断具体错误类型(TypeScript 会自动收窄类型)
if (isNetworkError(error)) {
console.log('Network failed:', error.networkType);
// TypeScript 现在知道 error 是 NetworkError
// 可以访问: networkType, code, cause 等
} else if (isHTTPError(error)) {
console.log('HTTP error:', error.status, error.statusText);
// TypeScript 现在知道 error 是 HTTPError
// 可以访问: status, statusText 等
} else if (isServerError(error)) {
console.log('Server error:', error.code, error.message);
// TypeScript 现在知道 error 是 ServerError
// 可以访问: code, message 等
} else if (isParseError(error)) {
console.log('Parse error:', error.cause);
// TypeScript 现在知道 error 是 ParseError
// 可以访问: cause, format 等
}
}
`
网络错误分为 8 种子类型:
| networkType | 说明 | 检测条件 |
|-------------|------|-----------|
| timeout | 请求超时 | "timeout", "timed out", "请求超时" |dns_failed
| | DNS 解析失败 | "enotfound", "getaddrinfo", "dns" |cors
| | CORS 错误 | "cors", "cross-origin" |offline
| | 离线状态 | "offline", "no internet" |connection_refused
| | 连接被拒绝 | "econnrefused", "connection refused" |connection_reset
| | 连接被重置 | "econnreset", "connection reset" |network_unreachable
| | 网络不可达 | "enetunreachable", "network unreachable" |unknown
| | 未知网络错误 | 其他网络错误 |
`typescript`
try {
const data = await akita.get('/api/data');
} catch (error) {
if (isNetworkError(error)) {
switch (error.networkType) {
case 'timeout':
showToast('请求超时,请重试');
break;
case 'offline':
showToast('网络未连接');
break;
case 'dns_failed':
showToast('DNS 解析失败');
break;
case 'connection_refused':
showToast('连接被拒绝');
break;
default:
showToast('网络错误: ' + error.code);
}
}
}
HTTP 错误自动检测所有 4xx/5xx 状态码:
`typescript
try {
const data = await akita.get('/api/data');
} catch (error) {
if (isHTTPError(error)) {
console.log('HTTP Status:', error.status);
console.log('Status Text:', error.statusText);
console.log('Error Code:', error.code); // HTTP_{status}
console.log('Request URL:', error.url);
console.log('Request Method:', error.method);
// 处理常见状态码
switch (error.status) {
case 401:
// Token 过期,跳转登录
window.location.href = '/login';
break;
case 403:
showToast('没有访问权限');
break;
case 404:
showToast('资源不存在');
break;
case 429:
showToast('请求过于频繁');
break;
case 500:
case 502:
case 503:
showToast('服务器错误,请稍后重试');
break;
}
}
}
`
当 JSON/text 解析失败时抛出解析错误:
`typescript
try {
const data = await akita.get('/api/data');
} catch (error) {
if (isParseError(error)) {
console.log('Parse error:', error.message);
console.log('Format:', error.code); // PARSE_JSON_ERROR 或 PARSE_TEXT_ERROR
console.log('Original error:', error.cause);
// 解析错误通常意味着服务器返回了无效数据
showToast('数据格式错误');
}
}
`
服务器错误由响应中的 error 字段触发(值为 '0'、'null'、'none' 时不会抛出错误):
`typescript
// 服务器返回: { error: 'User not found', code: 'USER_NOT_FOUND', userId: 123 }
try {
const user = await akita.get('/api/users/999');
} catch (error) {
if (isServerError(error)) {
console.log('Server error:', error.message); // 'User not found'
console.log('Server code:', error.code); // 'USER_NOT_FOUND' 或 'SERVER_ERROR'
// 服务器可能返回自定义错误代码
switch (error.code) {
case 'USER_NOT_FOUND':
showToast('用户不存在');
break;
case 'INVALID_TOKEN':
showToast('Token 无效');
break;
case 'PERMISSION_DENIED':
showToast('权限不足');
break;
default:
showToast('服务器错误: ' + error.message);
}
}
}
// 安全值 - 不会抛出错误
const data = await akita.get('/api/users');
// 服务器返回: { error: '0', data: [...] } ✅ 正常返回
// 服务器返回: { error: 'null', data: [...] } ✅ 正常返回
// 服务器返回: { error: 'none', data: [...] } ✅ 正常返回
`
`typescript
import akita, {
isAkitaError,
isNetworkError,
isHTTPError,
isParseError,
isServerError
} from 'akita';
async function makeRequest() {
try {
const data = await akita.get('/api/data');
console.log('Success:', data);
} catch (error) {
// 判断是否为 AkitaError
if (!isAkitaError(error)) {
console.error('Unknown error:', error);
return;
}
// 网络错误
if (isNetworkError(error)) {
console.error('Network error:', error.networkType);
showToast('网络错误,请检查网络连接');
return;
}
// HTTP 错误
if (isHTTPError(error)) {
console.error('HTTP error:', error.status);
if (error.status === 401) {
// 跳转登录
window.location.href = '/login';
} else if (error.status >= 500) {
showToast('服务器错误');
} else {
showToast('请求失败: ' + error.statusText);
}
return;
}
// 解析错误
if (isParseError(error)) {
console.error('Parse error:', error.code);
showToast('数据格式错误');
return;
}
// 服务器错误
if (isServerError(error)) {
console.error('Server error:', error.code);
showToast(error.message);
return;
}
}
}
`
除了使用 try/catch,也可以先检查响应状态:
`typescript
// 检查请求是否成功
const ok = await akita.get('/api/users').ok();
if (ok) {
console.log('Request succeeded');
} else {
console.log('Request failed');
}
// 获取状态码
const status = await akita.get('/api/users').status();
console.log('Status:', status);
// 获取状态文本
const statusText = await akita.get('/api/users').statusText();
console.log('Status Text:', statusText);
// 获取响应头
const headers = await akita.get('/api/users').headers();
console.log('Content-Type:', headers.get('Content-Type'));
// 获取响应大小
const size = await akita.get('/api/users').size();
console.log('Response size:', size);
`
基于错误类型实现智能重试:
`typescript
async function retryRequest
fn: () => Promise
options: { maxRetries?: number; retryNetwork?: boolean; retry500?: boolean } = {}
): Promise
const { maxRetries = 3, retryNetwork = true, retry500 = true } = options;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) {
throw error; // 最后一次重试也失败,抛出错误
}
// 判断是否应该重试
if (isNetworkError(error) && retryNetwork) {
console.log(Network error, retrying (${i + 1}/${maxRetries})...);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
continue;
}
if (isHTTPError(error) && error.status >= 500 && retry500) {
console.log(Server error, retrying (${i + 1}/${maxRetries})...);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
continue;
}
// 其他错误不重试,直接抛出
throw error;
}
}
throw new Error('Max retries exceeded');
}
// 使用示例
const data = await retryRequest(
() => akita.get('/api/data'),
{ maxRetries: 3, retryNetwork: true, retry500: true }
);
`
`typescript
const client = akita.create({
qsOptions: {
arrayFormat: 'indices', // ids[0]=1&ids[1]=2
encode: false, // 不编码参数
skipNulls: true, // 跳过 null 值
addQueryPrefix: true // 自动添加 ?
}
});
await client.get('/api', { query: { ids: [1, 2, 3], name: null } });
// -> /api?ids[0]=1&ids[1]=2&ids[2]=3
`
`typescript
import http from 'http';
import https from 'https';
// Keep-Alive
const agent = new http.Agent({ keepAlive: true });
await client.get('/api/data', { agent });
// 禁用 Keep-Alive
const agent = new http.Agent({ keepAlive: false });
await client.get('/api/data', { agent });
// 动态返回 agent
await client.get('/api/data', {
agent: (url) => {
if (url.protocol === 'https:') {
return new https.Agent({ keepAlive: true, rejectUnauthorized: false });
}
return new http.Agent({ keepAlive: true });
}
});
`
`typescript
// 自动跟随重定向(默认)
const data = await client.get('/redirect');
// 手动处理重定向
const res = await client.get('/redirect', { redirect: 'manual' }).response();
const location = res.headers.get('Location');
console.log('Redirect to:', location);
// 遇到重定向时拒绝
try {
const data = await client.get('/redirect', { redirect: 'error' });
} catch (error) {
console.log('Redirect detected');
}
`
`typescript`
const data = await client.get('/redirect', {
follow: 5 // 最多跟随 5 次重定向
});
`typescript
// 启用压缩(默认)
const data = await client.get('/api/data', { compress: true });
// 禁用压缩
const data = await client.get('/api/data', { compress: false });
`
`typescript
// 限制 1MB
const data = await client.get('/api/data', { size: 1024 * 1024 });
// 不限制(默认)
const data = await client.get('/api/data', { size: 0 });
`
`typescript
import akita from 'akita/lib/node';
// 或直接 import akita from 'akita' (默认使用 node 入口)
const data = await akita.get('https://api.example.com/data');
`
Node.js 特有功能:
- HTTP Agent 配置
- 重定向控制
- 超时设置
- 响应大小限制
- 压缩控制
`typescript
import akita from 'akita';
const data = await akita.get('/api/data');
`
浏览器特有功能:
- Blob 支持
- CORS 配置
- Cookie 配置
`typescript
import akita from 'akita/lib';
const data = await akita.get('https://api.example.com/data');
`
微信小程序限制:
- 不支持 streaming(调用 res.body 或 res.blob 会抛出错误)akita-method: PATCH
- PATCH 方法通过 header hack 实现(使用 头)
`javascript`
// 微信小程序中的 PATCH 请求
// 会被自动转换为 PUT 请求,并添加 akita-method: PATCH 头
await client.patch('/api/resource', {
body: { name: 'updated' }
});
`typescript
import akita from 'akita';
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
interface PaginatedResponse
data: T[];
total: number;
page: number;
limit: number;
}
// 创建 API 客户端
const api = akita.create({
apiRoot: 'https://api.example.com/v1',
init: {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
},
// 自动添加认证 token
onRequest: async (request) => {
const token = localStorage.getItem('auth_token');
if (token) {
request.init.headers['Authorization'] = Bearer ${token};
}
},
// 处理 401 错误
onResponse: async (request) => {
if (request.res?.status === 401) {
localStorage.removeItem('auth_token');
window.location.href = '/login';
}
},
// 转换日期字段
onDecode: async (request) => {
if (request.value?.createdAt) {
request.value.createdAt = new Date(request.value.createdAt);
}
if (request.value?.updatedAt) {
request.value.updatedAt = new Date(request.value.updatedAt);
}
},
// 提取分页数据
onDecode: async (request) => {
if (request.value?.data) {
request.value = request.value;
}
}
});
// User API
const UserAPI = {
// 获取用户列表
list: async (page = 1, limit = 10): Promise
return await api.get
query: { page, limit }
});
},
// 获取用户详情
get: async (id: number): Promise
return await api.get);
},
// 创建用户
create: async (data: Partial
return await api.post
},
// 更新用户
update: async (id: number, data: Partial
return await api.put, { body: data });
},
// 部分更新
patch: async (id: number, data: Partial
return await api.patch, { body: data });
},
// 删除用户
delete: async (id: number): Promise
await api.delete(/users/${id});
}
};
// 使用 API
async function main() {
// 获取用户列表
const users = await UserAPI.list(1, 20);
console.log('Users:', users.data);
console.log('Total:', users.total);
// 创建用户
const newUser = await UserAPI.create({
name: 'John Doe',
email: 'john@example.com'
});
console.log('Created user:', newUser);
// 更新用户
const updatedUser = await UserAPI.update(newUser.id, {
name: 'John Smith'
});
console.log('Updated user:', updatedUser);
// 删除用户
await UserAPI.delete(newUser.id);
console.log('User deleted');
}
main().catch(console.error);
`
`typescript
import akita from 'akita';
import fs from 'fs';
const uploadClient = akita.create({
apiRoot: 'https://upload.example.com/v1',
onRequest: async (request) => {
const token = await getUploadToken();
request.init.headers['Authorization'] = Bearer ${token};Upload progress: ${(progress * 100).toFixed(1)}%
},
onProgress: (progress) => {
console.log();
}
});
interface UploadResult {
id: string;
url: string;
filename: string;
size: number;
}
// 上传单个文件
async function uploadFile(filePath: string): Promise
const fileStream = fs.createReadStream(filePath);
const filename = filePath.split('/').pop();
const result = await uploadClient.post
body: {
file: fileStream,
filename,
description: 'File upload via Akita'
}
});
return result;
}
// 上传多个文件
async function uploadFiles(filePaths: string[]): Promise
const uploads = filePaths.map(async (path) => {
const fileStream = fs.createReadStream(path);
const filename = path.split('/').pop();
return await uploadClient.post
body: {
file: fileStream,
filename
}
});
});
return await Promise.all(uploads);
}
// 使用示例
uploadFile('./document.pdf')
.then(result => {
console.log('Upload successful:', result);
console.log('File URL:', result.url);
})
.catch(console.error);
`
`typescript
import akita from 'akita';
interface StockEvent {
type: 'ADDED' | 'MODIFIED' | 'REMOVED';
object: {
id: string;
symbol: string;
price: number;
change: number;
};
}
const streamClient = akita.create({
apiRoot: 'https://api.example.com',
onDecode: async (request) => {
// 转换价格字段
if (request.value?.price) {
request.value.price = parseFloat(request.value.price);
}
}
});
// 处理股票价格流
async function watchStock(symbol: string) {
const stream = await streamClient.get).jsonStream();
stream.on('data', (event) => {
const { type, object } = event;
switch (type) {
case 'ADDED':
console.log(🟢 New stock: ${object.symbol} @ $${object.price});${arrow} ${object.symbol} @ $${object.price} (${object.change}%)
break;
case 'MODIFIED':
const arrow = object.change >= 0 ? '📈' : '📉';
console.log();🔴 Delisted: ${object.symbol}
break;
case 'REMOVED':
console.log();
break;
}
});
stream.on('error', (error) => {
console.error('Stream error:', error.message);
});
stream.on('close', () => {
console.log('Stream closed, reconnecting...');
setTimeout(() => watchStock(symbol), 5000);
});
}
// 启动监控
watchStock('AAPL');
`
Akita 提供完整的 TypeScript 类型定义:
`typescript
import akita from 'akita';
// 类型推断
const users = await akita.get('/users');
// users 的类型自动推断为 any
// 显式指定泛型
interface User { id: number; name: string; }
const users = await akita.get
// users 的类型是 User[]
// Request 类型
const request = akita.get
console.log(request.url); // string
console.log(request.init); // RequestInit
console.log(request.res); // Response | undefined
console.log(request.raw); // string | undefined
console.log(request.value); // User[] | undefined
// 使用 Reducer 时的类型推断
const userIds = await akita.get
json.map(u => u.id)
);
// userIds 的类型是 number[]
`
详细的 API 文档请参考 index.d.ts 文件,其中包含所有接口、类型和方法的详细说明。
- ClientOptions - 客户端配置选项RequestInit
- - 请求配置参数Request
- - 请求对象(继承自 Promise)JsonStream
- - JSON 数据流Reducer
- - 数据处理器RequestHook
- - 请求钩子函数ProgressHook
- - 进度钩子函数
- create(options) - 创建新客户端实例resolve(key)
- - 获取或创建共享客户端实例setOptions(options)
- - 更新客户端配置request(path, init, reducer)
- - 通用请求方法get(path, init)
- - GET 请求post(path, init)
- - POST 请求put(path, init)
- - PUT 请求patch(path, init)
- - PATCH 请求delete(path, init)
- - DELETE 请求on(event, hook)
- - 添加事件监听off(event, hook)
- - 移除事件监听
- data() - 获取 JSON 数据(可省略)text()
- - 获取原始文本buffer()
- - 获取 Buffer 数据blob()
- - 获取 Blob 数据stream()
- - 获取流数据jsonStream
- - 获取 JSON 数据流response()
- - 获取 Response 对象ok()
- - 判断请求是否成功status()
- - 获取状态码statusText()
- - 获取状态文本size()
- - 获取数据大小headers()
- - 获取响应头
使用 AbortController:
`typescript
const controller = new AbortController();
const promise = client.get('/api/data', { signal: controller.signal });
// 取消请求
controller.abort();
`
`typescript`
const results = await Promise.all([
client.get('/api/users'),
client.get('/api/products'),
client.get('/api/orders')
]);
`typescriptRetry ${i + 1}/${maxRetries}
async function retryRequest
fn: () => Promise
maxRetries = 3
): Promise
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log();
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
throw new Error('Max retries exceeded');
}
const data = await retryRequest(() => client.get('/api/data'));
`
`typescript
const cache = new Map
async function cachedGet
if (cache.has(url)) {
return cache.get(url);
}
const data = await client.get
cache.set(url, data);
return data;
}
``
MIT
https://github.com/liangxingchen/akita