Hook-Fetch
安装
npm install hook-fetch
yarn add hook-fetch
pnpm add hook-fetch
基础使用
发起简单请求
import hookFetch from 'hook-fetch';
const response = await hookFetch('https://example.com/api/data').json();
console.log(response);
const postResponse = await hookFetch('https://example.com/api/data', {
method: 'POST',
data: { name: 'hook-fetch' }
}).json();
创建实例
const api = hookFetch.create({
baseURL: 'https://example.com',
headers: {
'Content-Type': 'application/json',
},
timeout: 5000,
});
const userData = await api.get('/users/1').json();
HTTP请求方法
const data = await api.get('/users', { page: 1, limit: 10 }).json();
const newUser = await api.post('/users', { name: 'John', age: 30 }).json();
const updatedUser = await api.put('/users/1', { name: 'John Doe' }).json();
const patchedUser = await api.patch('/users/1', { age: 31 }).json();
const deleted = await api.delete('/users/1').json();
const headers = await api.head('/users/1');
const options = await api.options('/users');
高级功能
响应处理
Hook-Fetch 支持多种响应数据处理方式:
const req = hookFetch('https://example.com/api/data');
const jsonData = await req.json();
const textData = await req.text();
const blobData = await req.blob();
const arrayBufferData = await req.arrayBuffer();
const formDataResult = await req.formData();
const bytesData = await req.bytes();
中断请求
const req = api.get('/long-running-process');
setTimeout(() => {
req.abort();
}, 1000);
请求重试
const req = api.get('/users/1');
req.abort();
const newReq = req.retry();
const result = await newReq.json();
流式数据处理
const req = hookFetch('https://sse.dev/test');
for await (const chunk of req.stream()) {
console.log(chunk.result);
}
插件系统
Hook-Fetch 提供了强大的插件系统,可以在请求生命周期的各个阶段进行干预:
function ssePlugin() {
const decoder = new TextDecoder('utf-8');
return {
name: 'sse',
async transformStreamChunk(chunk, config) {
if (!chunk.error) {
chunk.result = decoder.decode(chunk.result, { stream: true });
}
return chunk;
}
};
}
api.use(ssePlugin());
const req = api.get('/sse-endpoint');
for await (const chunk of req.stream<string>()) {
console.log(chunk.result);
}
插件生命周期示例
function examplePlugin() {
return {
name: 'example',
priority: 1,
async beforeRequest(config) {
config.headers = new Headers(config.headers);
config.headers.set('authorization', `Bearer ${tokenValue}`);
return config;
},
async afterResponse(context) {
if (context.responseType === 'json') {
if (context.result.code === 200) {
return context;
}
else {
throw new ResponseError({
message: context.result.message,
status: context.result.code,
response: context.response,
config: context.config,
name: 'BusinessError'
});
}
}
return context;
},
async beforeStream(body, config) {
return body;
},
async transformStreamChunk(chunk, config) {
if (!chunk.error) {
chunk.result = `Processed: ${chunk.result}`;
}
return chunk;
},
async onError(error) {
if (error.name === 'BusinessError') {
console.error(`业务错误: ${error.message}`);
}
else if (error.status === 401) {
console.error('登录已过期,请重新登录');
}
return error;
},
async onFinally(context, config) {
console.log(`Request to ${config.url} completed`);
}
};
}
业务场景封装示例
function createRequest() {
const request = hookFetch.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
const responseInterceptor = () => ({
name: 'response-interceptor',
async afterResponse(context) {
const { result } = context;
if (result.code === 0) {
return result.data;
}
throw new Error(result.message);
}
});
const errorHandler = () => ({
name: 'error-handler',
async onError(error) {
if (error.status === 401) {
window.location.href = '/login';
return;
}
if (error.status === 403) {
window.location.href = '/403';
return;
}
console.error(error.message);
return error;
}
});
const requestLogger = () => ({
name: 'request-logger',
async beforeRequest(config) {
console.log(`Request: ${config.method} ${config.url}`, config);
return config;
},
async afterResponse(context) {
console.log(`Response: ${context.response.status}`, context.result);
return context;
}
});
request.use(responseInterceptor());
request.use(errorHandler());
request.use(requestLogger());
return {
user: {
getInfo: () => request.get('/user/info').json(),
updateInfo: data => request.put('/user/info', data).json(),
changePassword: data => request.post('/user/password', data).json()
},
order: {
getList: params => request.get('/orders', params).json(),
create: data => request.post('/orders', data).json(),
cancel: id => request.post(`/orders/${id}/cancel`).json()
}
};
}
const api = createRequest();
const userInfo = await api.user.getInfo();
const order = await api.order.create({
productId: 1,
quantity: 2
});
插件钩子函数:
beforeRequest
: 请求发送前处理配置,可以返回新的配置或直接修改配置
afterResponse
: 响应接收后处理数据,可以返回新的响应或直接修改响应
beforeStream
: 流式请求开始时的处理,用于初始化或转换流
transformStreamChunk
: 处理流式数据块,可以返回新的数据块或直接修改数据块
onError
: 处理请求错误,可以返回新的错误或直接修改错误
onFinally
: 请求完成后的回调,用于清理资源等操作
所有生命周期钩子都支持同步和异步操作,可以根据需要返回 Promise 或直接返回值。每个钩子函数都会接收到当前的配置对象(config),可以用于判断和处理不同的请求场景。
泛型支持
Hook-Fetch 提供了完善的TypeScript类型支持,可以为请求和响应定义明确的类型:
interface BaseResponseVO {
code: number;
data: never;
message: string;
}
const request = hookFetch.create<BaseResponseVO, 'data'>({
baseURL: 'https://example.com',
headers: {
'Content-Type': 'application/json',
},
timeout: 5000,
});
interface User {
id: number;
name: string;
email: string;
}
const res = await request.get<User>('/users/1').json();
console.log(res.data);
完整API
请求配置选项
interface RequestOptions {
baseURL: string;
timeout: number;
headers: HeadersInit;
plugins: Array<HookFetchPlugin>;
withCredentials: boolean;
params: any;
data: any;
controller: AbortController;
extra: any;
qsArrayFormat: 'indices' | 'brackets' | 'repeat' | 'comma';
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
}
插件类型
interface HookFetchPlugin<T = unknown, E = unknown, P = unknown, D = unknown> {
name: string;
priority?: number;
beforeRequest?: (config: RequestConfig<P, D, E>) => Promise<RequestConfig<P, D, E>> | RequestConfig<P, D, E>;
afterResponse?: (context: FetchPluginContext<T, E, P, D>, config: RequestConfig<P, D, E>) => Promise<FetchPluginContext<T, E, P, D>> | FetchPluginContext<T, E, P, D>;
beforeStream?: (body: ReadableStream<any>, config: RequestConfig<P, D, E>) => Promise<ReadableStream<any>> | ReadableStream<any>;
transformStreamChunk?: (chunk: StreamContext<any>, config: RequestConfig<P, D, E>) => Promise<StreamContext> | StreamContext;
onError?: (error: Error, config: RequestConfig<P, D, E>) => Promise<Error | void | ResponseError<E>> | Error | void | ResponseError<E>;
onFinally?: (context: FetchPluginContext<T, E, P, D>, config: RequestConfig<P, D, E>) => Promise<void> | void;
}
Vue Hooks
Hook-Fetch 提供了 Vue 组合式 API 的支持,可以更方便地在 Vue 组件中使用:
import hookFetch from 'hook-fetch';
import { useHookFetch } from 'hook-fetch/vue';
const api = hookFetch.create({
baseURL: 'https://api.example.com'
});
const YourComponent = defineComponent({
setup() {
const { request, loading, cancel, text, stream, blob, arrayBufferData, formDataResult, bytesData } = useHookFetch({
request: api.get,
onError: (error) => {
console.error('请求错误:', error);
}
});
const fetchData = async () => {
const response = await request('/users').json();
console.log(response);
};
const fetchText = async () => {
const text = await text('/text');
console.log(text);
};
const handleStream = async () => {
for await (const chunk of stream('/stream')) {
console.log(chunk);
}
};
const handleCancel = () => {
cancel();
};
return {
loading,
fetchData,
fetchText,
handleStream,
handleCancel
};
}
});
React Hooks
Hook-Fetch 同样提供了 React Hooks 的支持,可以在 React 组件中方便地使用:
import { useHookFetch } from 'hook-fetch/react';
import hookFetch from 'hook-fetch';
const api = hookFetch.create({
baseURL: 'https://api.example.com'
});
const YourComponent = () => {
const { request, loading, setLoading, cancel, text, stream, blob, arrayBufferData, formDataResult, bytesData } = useHookFetch({
request: api.get,
onError: (error) => {
console.error('请求错误:', error);
}
});
const fetchData = async () => {
const response = await request('/users').json();
console.log(response);
};
const fetchText = async () => {
const text = await text('/text');
console.log(text);
};
const handleStream = async () => {
for await (const chunk of stream('/stream')) {
console.log(chunk);
}
};
const handleCancel = () => {
cancel();
};
return (
<div>
<div>加载状态: {loading ? '加载中' : '已完成'}</div>
<button onClick={fetchData}>获取数据</button>
<button onClick={fetchText}>获取文本</button>
<button onClick={handleStream}>处理流</button>
<button onClick={handleCancel}>取消请求</button>
</div>
);
};
vscode提示插件的引用路径
注意事项
- Hook-Fetch 需要显式调用
.json()
方法来解析JSON响应
- 所有的请求方法都返回Promise对象
- 可以通过
.retry()
方法重试已中断的请求
- 插件按照优先级顺序执行
预计开发内容
📝 贡献指南
欢迎提交issue
或pull request
,共同完善Hook-Fetch
。
📄 许可证
MIT
联系我们