iota-fetch
v0.2.0
Published
一个基于 **axios** 的二次封装请求库,提供统一的 Axios Method 请求调用方式,并内置**请求队列**能力(可按 key 中断单个请求或中断全部请求)。支持自定义 axios 默认配置、请求/响应拦截器,以及可选的 loading 显示/隐藏钩子。
Readme
iota-fetch
一个基于 axios 的二次封装请求库,提供统一的 Axios Method 请求调用方式,并内置请求队列能力(可按 key 中断单个请求或中断全部请求)。支持自定义 axios 默认配置、请求/响应拦截器,以及可选的 loading 显示/隐藏钩子。
安装
npm i iota-fetch axios本项目本身依赖
axios,如果你在业务工程里已安装 axios,可按你的依赖管理策略处理(避免重复安装/版本冲突)。
快速开始
import { createFetch } from "iota-fetch";
const fetch = createFetch({
requestConfig: {
baseURL: "https://api.example.com",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
},
});
// GET:第二个参数会作为 query params
const list = await fetch.get("/items", { page: 1, pageSize: 10 });
// POST:第二个参数会作为 body data
const created = await fetch.post("/items", { name: "foo" });TypeScript 返回值
请求方法支持通过泛型声明响应数据类型。封装会自动返回 response.data,不需要再手动读取 Axios 响应对象:
type Item = {
id: string;
name: string;
};
const item = await fetch.get<undefined, Item>("/items/1");
item.id;请求级配置类型也可以直接从包中导入:
import type { ConfigProps } from "iota-fetch";
const config: ConfigProps = {
timeout: 5000,
loading: true,
};请求级配置
第三个参数支持收缩后的 ConfigProps,会覆盖实例级配置。例如可以单独设置超时或请求头:
const result = await fetch.get("/items", { page: 1 }, {
timeout: 5000,
headers: { "X-Trace-Id": "trace-id" },
});config.loading 是本封装提供的扩展字段,只控制 loading 钩子,不会被发送给 Axios adapter。请求级 params 和 data 会分别由无请求体方法与有请求体方法的第二个参数生成。
API
createFetch(options)
创建一个包含全部 Axios Method 请求方法的请求对象。
参数 options
requestConfig:CreateAxiosDefaults- axios 实例默认配置(如
baseURL、timeout、headers等)
- axios 实例默认配置(如
requestIntercept?:() => { onFulfilled, onRejected }- 请求拦截器工厂函数(内部会
instance.interceptors.request.use(...))
- 请求拦截器工厂函数(内部会
responseIntercept?:() => { onFulfilled, onRejected }- 响应拦截器工厂函数(内部会
instance.interceptors.response.use(...))
- 响应拦截器工厂函数(内部会
loading?:{ show(): void; hide(): void }- 传入后可配合单次请求的
config.loading控制显示/隐藏
- 传入后可配合单次请求的
cancelRequest?:boolean- 是否启用请求取消和同 key 自动取消旧请求,默认值为
true
- 是否启用请求取消和同 key 自动取消旧请求,默认值为
requestQueue?:RequestQueue- 可选的共享队列。默认每个
createFetch实例使用独立队列,只有明确传入同一个队列时才会跨实例取消请求
- 可选的共享队列。默认每个
requestConfig 使用 Axios 的 CreateAxiosDefaults 类型,常用配置包括 baseURL、timeout、headers、withCredentials 和自定义 adapter。拦截器工厂函数会在创建实例时执行一次。
如果需要允许相同请求并发执行,可以关闭请求取消:
const fetch = createFetch({
requestConfig: { baseURL: "https://api.example.com" },
cancelRequest: false,
});关闭后不会注册请求队列,也不会自动取消旧请求;对应实例的 fetch.requestQueue.abortive() 和 fetch.requestQueue.removeResAll() 只会作用于启用 cancelRequest 创建的请求。
返回值
返回 fetch 对象:
fetch.get(url, data?, config?)fetch.delete(url, data?, config?)fetch.head(url, data?, config?)fetch.options(url, data?, config?)fetch.post(url, data?, config?)fetch.put(url, data?, config?)fetch.patch(url, data?, config?)fetch.purge(url, data?, config?)fetch.link(url, data?, config?)fetch.unlink(url, data?, config?)fetch.query(url, data?, config?)
其中:
url: stringdata?: unknownconfig?: ConfigProps
行为规则:
get/delete/head/options:data会被放到paramspost/put/patch/purge/link/unlink/query:data会被放到data- 返回值:默认
Promise<R>,内部返回response.data
ConfigProps 是收缩后的请求级配置类型,不能覆盖封装管理的 url、method、data、params 和 signal;这些值分别由方法参数、请求方法和请求队列统一控制。
例如:
await fetch.head("/items");
await fetch.options("/items");
await fetch.purge("/items/1", { reason: "expired" });
await fetch.link("/items/1", { related: "/owners/1" });
await fetch.unlink("/items/1", { related: "/owners/1" });
await fetch.query("/items", { filter: { active: true } });请求失败时会保留 Axios 原始错误对象,调用方可以使用 axios.isAxiosError(error) 或检查 error.response、error.code:
try {
const result = await fetch.get<undefined, Item[]>("/items");
} catch (error) {
if (axios.isAxiosError(error)) {
console.error(error.response?.status, error.message);
}
}如果使用上面的错误处理示例,请额外导入 axios:
import axios from "axios";拦截器示例
import type { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from "axios";
import { createFetch } from "iota-fetch";
const fetch = createFetch({
requestConfig: { baseURL: "https://api.example.com" },
requestIntercept: () => ({
onFulfilled: (config: InternalAxiosRequestConfig) => {
config.headers.set("X-Token", "your-token");
return config;
},
onRejected: (error: AxiosError) => Promise.reject(error),
}),
responseIntercept: () => ({
onFulfilled: (resp: AxiosResponse) => resp,
onRejected: (error: AxiosError) => Promise.reject(error),
}),
});Loading 示例
import { createFetch } from "iota-fetch";
const loading = {
show: () => console.log("loading show"),
hide: () => console.log("loading hide"),
};
const fetch = createFetch({
requestConfig: { baseURL: "https://api.example.com" },
loading,
});
// 只有当 config.loading 为 true 才会触发 show/hide
await fetch.get("/items", { page: 1 }, { loading: true });Loading 并发计数建议
封装层会为每个启用 config.loading 的请求调用一次 show(),并在请求成功、失败或取消后调用一次 hide()。建议在 loading 组件内部维护并发计数:只有第一个请求开始时显示 loading,所有请求结束后再隐藏 loading。
let activeRequests = 0;
const loading = {
show() {
activeRequests += 1;
if (activeRequests === 1) {
openLoading();
}
},
hide() {
activeRequests = Math.max(0, activeRequests - 1);
if (activeRequests === 0) {
closeLoading();
}
},
};openLoading 和 closeLoading 替换为业务组件实际的显示/隐藏方法即可。计数逻辑应放在 loading 组件中,不建议在每个请求调用处重复维护;这样多个并发请求、请求失败和请求取消都能保持正确的 loading 状态。
请求队列与取消请求
每个 createFetch 实例都会拥有独立的请求队列,内部用 AbortController 管理请求。可以通过返回对象上的 fetch.requestQueue 获取当前实例队列:
// 获取当前队列快照(ReadonlyMap<key, AbortController>)
const q = fetch.requestQueue.getQueue();
console.log(q.size);getQueue() 返回的是快照,修改这个 Map 不会改变内部队列。请求完成后会自动移除对应项。
如果业务确实需要多个实例共享取消流程,可以显式传入同一个 RequestQueue:
import { createFetch, RequestQueue } from "iota-fetch";
const sharedQueue = new RequestQueue();
const userFetch = createFetch({ requestConfig: { baseURL: "/user" }, requestQueue: sharedQueue });
const orderFetch = createFetch({ requestConfig: { baseURL: "/order" }, requestQueue: sharedQueue });包仍然导出 requestQueue 作为独立的共享队列实例,但它不会自动接管新建 fetch 实例的请求。
key 规则
key 默认由以下规则生成:
- 基础部分:
method + "-" + url - 数据部分:如果存在
data,则对其进行以下处理:- 递归排序对象键
- 清洗数据(过滤
undefined和函数) - 将处理后的数据转换为 JSON 字符串
- 使用 MD5 生成哈希值
- 最终 key:
基础部分 + "-" + MD5哈希值
这样处理的好处是:
- 相同的请求参数(无论顺序如何)会生成相同的 key
- 避免了复杂参数导致 key 过长的问题
- 提高了 key 的唯一性和安全性
你也可以手动生成:
const key = fetch.requestQueue.createKey("get", "/items", { page: 1 });示例:
// 以下两个请求会生成相同的 key
const key1 = fetch.requestQueue.createKey("get", "/items", { page: 1, size: 10 });
const key2 = fetch.requestQueue.createKey("get", "/items", { size: 10, page: 1 });
// 输出示例:"get-/items-5f4dcc3b5aa765d61d8327deb882cf99"中断单个请求
fetch.requestQueue.abortive(key);中断所有请求
fetch.requestQueue.removeResAll();同 key 自动取消旧请求(内置行为)
当 cancelRequest 开启时,发起新请求如果队列里已存在同一个 key,会先取消旧请求再登记新请求(避免相同请求并发造成的覆盖与浪费)。
注意:是否算“同一个请求”取决于 key(包含
method/url/data的 JSON 字符串)。
开启 cancelRequest 时,同 key 的新请求会自动取消旧请求。若需要保留两个相同 URL 但参数不同的请求,请确保第二个参数不同;也可以通过 fetch.requestQueue.createKey() 获取对应 key 后手动调用 fetch.requestQueue.abortive()。
开发与构建
# 构建产物到 lib/(CJS + ESM + d.ts)
npm run build
# 开发模式(watch + 本地静态服务 + livereload)
npm run start
# 自动发布脚本(检查代码更新、npm 登录状态、版本管理)
npm run release构建输出(默认):
lib/bundle.cjs.jslib/bundle.esm.jslib/main.d.ts
发布流程
- 运行
npm run release启动发布流程 - 脚本会检查代码是否有更新
- 检查 npm 是否登录
- 显示当前版本和仓库版本
- 选择版本升级类型(小版本/中版本/大版本)
- 执行构建和发布
- 显示远程包的最新版本
版本管理
- 小版本:修复 bug,向后兼容
- 中版本:添加新功能,向后兼容
- 大版本:不向后兼容的变更
