net-vert
v1.0.0
Published
Dependency Inversion Network Library with Type-Safe Injection.
Readme
@net-vert/core
轻量级依赖倒置网络请求库,专为扩展和易用而设计。
GitHub 开源仓库 👉 https://github.com/yvygyyth/net-vert
✨ 核心特性
✅ 依赖倒置设计 - 解耦网络层,按需注入 axios、fetch 或自定义请求器
✅ 中间件扩展 - 内置缓存、幂等、重试、并发控制、同步模式等强大中间件
✅ 类型安全 - TypeScript 全类型提示,开发体验丝滑
✅ 零配置上手 - API 极简,开箱即用
✅ 灵活组合 - 多种中间件自由组合,满足复杂业务场景
✅ Tree-Shaking - 支持按需引入,打包体积更小
📦 安装
npm install @net-vert/core或者使用其他包管理器:
pnpm add @net-vert/core
# 或
yarn add @net-vert/core🚀 快速上手
1️⃣ 注入请求器
首先,将你的请求函数注入到 @net-vert/core。这个函数接收请求配置,返回一个 Promise:
import { inject } from '@net-vert/core'
// 创建一个简单的请求函数
const myRequestor = (config) => {
// 返回一个 Promise
return new Promise((resolve, reject) => {
// 这里可以是任何异步请求实现
// 例如:fetch、axios、小程序的 wx.request 等
fetch(config.url, {
method: config.method,
headers: config.headers,
body: config.data ? JSON.stringify(config.data) : undefined
})
.then(res => res.json())
.then(data => resolve(data))
.catch(err => reject(err))
})
}
// 注入到 net-vert
inject(myRequestor)提示:你可以注入任何符合请求器签名
(config) => Promise的函数,包括 axios、fetch 或自定义请求实现。
2️⃣ 发起请求
注入完成后,使用 useRequestor 或 createRequestor 创建请求器:
基础用法
import { useRequestor } from '@net-vert/core'
const requestor = useRequestor()
// GET 请求
requestor.get('/user/info', { params: { id: 1 } }).then(console.log)
// POST 请求
requestor.post('/user/create', { name: 'Alice' }).then(console.log)
// PUT 请求
requestor.put('/user/update', { id: 1, name: 'Bob' })
// DELETE 请求
requestor.delete('/user/delete', { params: { id: 1 } })🛠 中间件系统
@net-vert/core 的强大之处在于其中间件系统。你可以通过 createRequestor 结合各种中间件来扩展请求能力。
核心 API:createRequestor
import { createRequestor, cache, idempotent } from '@net-vert/core'
const requestor = createRequestor({
extensions: [
idempotent(), // 防止并发重复请求
cache({ duration: 5000 }) // 缓存 5 秒
]
})
// 使用增强后的请求器
requestor.get('/api/data')📚 内置中间件
1. 缓存中间件 (cache)
为请求结果添加缓存能力,避免重复请求相同数据。
基础用法
import { createRequestor, cache } from '@net-vert/core'
const requestor = createRequestor({
extensions: [
cache({
duration: 5000 // 缓存 5 秒
})
]
})
// 首次请求会发起网络请求
await requestor.get('/api/users')
// 5 秒内的相同请求会直接返回缓存
await requestor.get('/api/users') // 使用缓存配置选项
interface CacheOptions<D = any, R = any> {
/**
* 缓存 key 生成函数
* 默认:基于 method + url + params 生成哈希
*/
key?: (ctx: { config: RequestConfig<D> }) => string
/**
* 缓存有效期(毫秒)
* - number: 固定时长
* - function: 动态计算(可根据响应内容决定缓存时长)
*/
duration?: number | ((ctx: {
key: string
config: RequestConfig<D>
response: R
}) => number)
/**
* 是否持久化到 IndexedDB 或 localStorage
* 默认:false(仅内存缓存)
*/
persist?: boolean
/**
* 缓存有效性校验函数
* 返回 false 则忽略缓存,重新请求
*/
isValid?: (ctx: {
key: string
config: RequestConfig<D>
cachedData?: ExpirableValue<R>
}) => boolean | Promise<boolean>
}高级示例
自定义缓存 key
const requestor = createRequestor({
extensions: [
cache({
duration: 5000,
// 只根据 URL 生成 key,忽略参数差异
key: ({ config }) => `custom_${config.url}`
})
]
})
// 这两个请求会共享缓存(因为 URL 相同)
await requestor.get('/api/users', { params: { id: 1 } })
await requestor.get('/api/users', { params: { id: 2 } }) // 使用缓存动态缓存时长
const requestor = createRequestor({
extensions: [
cache({
// 根据响应内容决定缓存时长
duration: ({ response }) => {
// 如果数据标记为"静态",缓存 1 小时
if (response.isStatic) {
return 60 * 60 * 1000
}
// 否则缓存 5 秒
return 5000
}
})
]
})自定义缓存有效性校验
let userLoggedOut = false
const requestor = createRequestor({
extensions: [
cache({
duration: 10000,
// 用户登出后使所有缓存失效
isValid: ({ cachedData }) => {
if (userLoggedOut) return false
return true
}
})
]
})持久化缓存
const requestor = createRequestor({
extensions: [
cache({
duration: 24 * 60 * 60 * 1000, // 缓存 24 小时
persist: true // 持久化到 IndexedDB/localStorage
})
]
})手动操作缓存
缓存中间件暴露了 storage 属性,允许你手动操作缓存:
const cacheMiddleware = cache({ duration: 5000 })
const requestor = createRequestor({
extensions: [cacheMiddleware]
})
// 清空所有缓存
cacheMiddleware.storage.clear()
// 获取缓存项
const cached = cacheMiddleware.storage.getItem('/api/users')
// 删除特定缓存
cacheMiddleware.storage.removeItem('/api/users')
// 手动设置缓存
cacheMiddleware.storage.set('/api/users', { data: [...] })2. 幂等中间件 (idempotent)
防止相同的请求并发执行,确保在前一个请求完成前,后续相同请求返回同一个 Promise。
基础用法
import { createRequestor, idempotent } from '@net-vert/core'
const requestor = createRequestor({
extensions: [idempotent()]
})
// 并发发起两个相同请求
const promise1 = requestor.get('/api/users')
const promise2 = requestor.get('/api/users')
// promise1 和 promise2 是同一个 Promise 实例
console.log(promise1 === promise2) // true
// 只会发起一次网络请求
const [result1, result2] = await Promise.all([promise1, promise2])配置选项
interface IdempotencyOptions<D = any> {
/**
* 自定义请求唯一标识生成函数
* 默认:基于 method + url + params 生成哈希
*/
genKey?: (config: RequestConfig<D>) => string
}高级示例
const requestor = createRequestor({
extensions: [
idempotent({
// 自定义 key 生成逻辑
genKey: (config) => `${config.method}:${config.url}`
})
]
})与缓存组合使用
幂等中间件通常与缓存中间件配合使用,实现"短期防重复 + 长期缓存":
const requestor = createRequestor({
extensions: [
idempotent(), // 防止并发重复(短期)
cache({ duration: 5000 }) // 缓存结果(长期)
]
})3. 重试中间件 (retry)
当请求失败时自动重试,支持固定延迟、指数退避等策略。
基础用法
import { createRequestor, retry } from '@net-vert/core'
const requestor = createRequestor({
extensions: [
retry({
retries: 3, // 最多重试 3 次
delay: 1000 // 每次重试延迟 1 秒
})
]
})
// 失败后会自动重试最多 3 次
await requestor.get('/api/unstable-endpoint')配置选项
interface RetryOptions<D = any> {
/**
* 最大重试次数
* 默认:3
*/
retries?: number
/**
* 重试延迟(毫秒)
* - number: 固定延迟
* - function: 动态延迟(实现指数退避等策略)
* 默认:0
*/
delay?: number | ((ctx: {
config: RequestConfig<D>
lastResponse: any
attempt: number
}) => number)
/**
* 重试条件判断函数
* 返回 true 则重试,false 则直接抛出错误
* 默认:所有错误都重试
*/
retryCondition?: (ctx: {
config: RequestConfig<D>
lastResponse: any
attempt: number
}) => boolean
}高级示例
指数退避重试
const requestor = createRequestor({
extensions: [
retry({
retries: 5,
// 指数退避:第 n 次重试延迟 2^n * 100ms
delay: ({ attempt }) => Math.pow(2, attempt) * 100
})
]
})条件重试(仅 5xx 错误)
const requestor = createRequestor({
extensions: [
retry({
retries: 3,
delay: 1000,
// 只在服务器错误时重试
retryCondition: ({ lastResponse }) => {
const status = lastResponse?.response?.status
return status >= 500 && status < 600
}
})
]
})4. 并发控制中间件 (concurrent)
限制同时发起的请求数量,适用于批量请求场景。
基础用法
import { createRequestor, concurrent } from '@net-vert/core'
const requestor = createRequestor({
extensions: [
concurrent({
parallelCount: 3 // 最多同时 3 个请求
})
]
})
// 发起 10 个请求,但同时只会执行 3 个
const promises = []
for (let i = 0; i < 10; i++) {
promises.push(requestor.get(`/api/data/${i}`))
}
await Promise.all(promises)配置选项
interface ConcurrentOptions<D = any> {
/**
* 最大并行请求数
* 默认:4
*/
parallelCount?: number
/**
* 任务唯一标识生成函数
* 默认:基于时间戳 + 随机数
*/
createId?: (config: RequestConfig<D>) => string | number
}高级示例
const requestor = createRequestor({
extensions: [
concurrent({
parallelCount: 5,
// 使用请求 URL 作为任务 ID
createId: ({ config }) => config.url
})
]
})与重试组合使用
const requestor = createRequestor({
extensions: [
concurrent({ parallelCount: 3 }), // 限制并发数
retry({ retries: 2, delay: 500 }) // 失败重试
]
})
// 批量请求,每个请求都有重试保护
const results = await Promise.all(
urls.map(url => requestor.get(url))
)5. 同步模式中间件 (sync)
让异步请求支持"Suspense 风格"的同步调用,适用于 React Suspense 等场景。
⚠️ 重要:
sync中间件必须放在中间件数组的第一位,否则会导致功能异常。详见 中间件顺序规则。
基础用法(Suspense 模式)
import { createRequestor, sync } from '@net-vert/core'
const requestor = createRequestor({
extensions: [sync()]
})
// 首次调用会抛出 Promise(触发 Suspense)
try {
const data = requestor.get('/api/users')
} catch (promise) {
await promise // 等待数据加载
}
// 再次调用会同步返回缓存数据
const data = requestor.get('/api/users') // 同步返回,不再抛出
console.log(data) // 直接获取数据React Suspense 集成
import { createRequestor, sync } from '@net-vert/core'
const requestor = createRequestor({
extensions: [sync()]
})
function UserProfile({ userId }) {
// 首次渲染会抛出 Promise,触发 Suspense
// 数据加载完成后重新渲染,此时同步返回数据
const user = requestor.get(`/api/users/${userId}`)
return <div>{user.name}</div>
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId={1} />
</Suspense>
)
}配置选项
interface SyncOptions<D = any, R = any> {
/**
* 缓存 key 生成函数
* 默认:基于 method + url + params 生成
*/
key?: (ctx: { config: RequestConfig<D> }) => string
/**
* 是否启用 Suspense 模式(抛出 Promise)
* - true: 首次调用抛出 Promise(默认)
* - false: 首次调用返回 Promise
*/
suspense?: boolean
/**
* 自定义 Promise 包装函数
* 可用于添加元数据或修改返回结构
*/
wrapSuspense?: (params: {
key: string
config: RequestConfig<D>
p: Promise<R>
}) => Promise<R>
/**
* 缓存有效期(毫秒)
* 默认:永久缓存
*/
duration?: number
/**
* 是否持久化
* 默认:false
*/
persist?: boolean
}高级示例
非 Suspense 模式
const requestor = createRequestor({
extensions: [
sync({ suspense: false }) // 关闭 Suspense 模式
]
})
// 首次调用返回 Promise
const promise = requestor.get('/api/users')
await promise
// 再次调用同步返回缓存
const data = requestor.get('/api/users') // 同步返回自定义 Promise 包装
const requestor = createRequestor({
extensions: [
sync({
wrapSuspense: ({ p, key }) => {
// 给 Promise 添加元数据
return p.then(data => ({
...data,
__cacheKey: key,
__timestamp: Date.now()
}))
}
})
]
})🔗 中间件组合
多个中间件可以自由组合,执行顺序遵循数组顺序:
const requestor = createRequestor({
extensions: [
idempotent(), // 1. 防止并发重复
cache({ duration: 5000 }), // 2. 缓存结果
retry({ retries: 3, delay: 1000 }), // 3. 失败重试
concurrent({ parallelCount: 3 }) // 4. 限制并发
]
})⚠️ 重要:中间件顺序规则
在组合中间件时,需要注意以下强制性规则,否则会导致功能异常:
同步模式中间件(
sync)必须放在第一位// ✅ 正确 const requestor = createRequestor({ extensions: [ sync(), // 必须第一个 idempotent(), cache({ duration: 5000 }) ] }) // ❌ 错误 - 某些情况下 sync 不在第一位会导致同步调用失败,幂等缓存的promise会被sync模块直接改变,导致幂等缓存失效.重试也会因为同步模块抛错误耗尽失败次数,同步模块的同步能力也可能因为部分中间件的异步特性而失效 const requestor = createRequestor({ extensions: [ retry() | idempotent(), sync() // 错误位置! ] })自定义中间件如果需要拦截所有请求,必须前置
自定义中间件的位置决定了它在中间件链中的执行时机:
- 前置:可以拦截和修改所有请求(包括被其他中间件处理的请求)
- 后置:只能处理未被前面中间件拦截的请求(如缓存命中的请求不会到达后置中间件)
const loggerMiddleware: Middleware = async ({ config, next }) => { console.log('Request:', config.url) return await next() } // ✅ 正确 - logger 在最前面,可以记录所有请求 const requestor = createRequestor({ extensions: [ loggerMiddleware, // 第一个执行 cache({ duration: 5000 }), retry({ retries: 3 }) ] }) // ⚠️ 注意 - logger 在 cache 之后,缓存命中的请求不会被记录 const requestor = createRequestor({ extensions: [ cache({ duration: 5000 }), loggerMiddleware, // 缓存命中时不会执行 retry({ retries: 3 }) ] })推荐的中间件顺序(从前到后):
sync()- 同步模式(如果使用)- 自定义拦截中间件(日志、鉴权等)
idempotent()- 幂等处理cache()- 缓存retry()- 重试concurrent()- 并发控制
常见组合模式
1. 数据查询场景(幂等 + 缓存)
const requestor = createRequestor({
extensions: [
idempotent(), // 防止并发重复请求
cache({ duration: 5000 }) // 缓存 5 秒
]
})2. 批量请求场景(并发控制 + 重试)
const requestor = createRequestor({
extensions: [
concurrent({ parallelCount: 5 }), // 最多同时 5 个请求
retry({ retries: 3, delay: 500 }) // 失败重试 3 次
]
})3. 全能组合(适用于复杂场景)
const requestor = createRequestor({
extensions: [
idempotent(), // 1. 防止并发重复
cache({
duration: 10000,
persist: true // 2. 持久化缓存 10 秒
}),
retry({
retries: 3,
delay: ({ attempt }) => Math.pow(2, attempt) * 100 // 3. 指数退避重试
}),
concurrent({ parallelCount: 3 }) // 4. 限制并发数
]
})🎯 便捷组合方法
为常见场景提供了预设组合:
createCachedIdempotentRequestor
创建带缓存和幂等的请求器,适用于数据查询接口。
import { createCachedIdempotentRequestor } from '@net-vert/core'
const requestor = createCachedIdempotentRequestor({
duration: 5000, // 缓存 5 秒
persist: false, // 不持久化
// 支持所有 cache 和 idempotent 的配置项
})
// 等价于:
// createRequestor({
// extensions: [
// idempotent(),
// cache({ duration: 5000, persist: false })
// ]
// })createConcurrentRetryRequestor
创建带并发控制和重试的请求器,适用于批量请求场景。
import { createConcurrentRetryRequestor } from '@net-vert/core'
const requestor = createConcurrentRetryRequestor({
parallelCount: 5, // 最多 5 个并发
retries: 3, // 重试 3 次
delay: 1000 // 每次延迟 1 秒
})
// 等价于:
// createRequestor({
// extensions: [
// concurrent({ parallelCount: 5 }),
// retry({ retries: 3, delay: 1000 })
// ]
// })🔑 多实例管理
支持注入和管理多个请求器实例:
import { inject, createRequestor } from '@net-vert/core'
// 注入主实例(默认)
inject(axiosAdapter)
// 注入备用实例
inject(fetchAdapter, 'backup')
// 使用默认实例
const requestor1 = createRequestor()
// 使用备用实例
const requestor2 = createRequestor({ instanceKey: 'backup' })📘 API 参考
核心 API
inject(requestor, instanceKey?)
注入请求器到全局容器。
- requestor:
(config: RequestConfig) => Promise<any>- 请求器函数 - instanceKey:
string | symbol- 实例标识(可选,默认为'default')
useRequestor(instanceKey?)
获取已注入的请求器。
- instanceKey:
string | symbol- 实例标识(可选) - 返回:
Requestor- 请求器对象
createRequestor(config?)
创建带中间件的请求器。
- config.extensions:
Middleware[]- 中间件数组 - config.instanceKey:
string | symbol- 使用的请求器实例标识 - 返回:
Requestor- 增强后的请求器
Requestor 接口
interface Requestor {
request<R = any, D = any>(config: RequestConfig<D>): Promise<R>
get<R = any, D = any>(url: string, config?: Omit<RequestConfig<D>, 'method' | 'url'>): Promise<R>
post<R = any, D = any>(url: string, data?: D, config?: Omit<RequestConfig<D>, 'method' | 'url'>): Promise<R>
put<R = any, D = any>(url: string, data?: D, config?: Omit<RequestConfig<D>, 'method' | 'url'>): Promise<R>
delete<R = any, D = any>(url: string, config?: Omit<RequestConfig<D>, 'method' | 'url'>): Promise<R>
}🎨 完整示例
示例 1:典型的前端应用配置
import axios from 'axios'
import { inject, createRequestor, idempotent, cache, retry } from '@net-vert/core'
// 1. 创建并注入 axios 实例
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
})
inject(config => instance.request(config))
// 2. 创建数据查询请求器(带缓存和幂等)
export const queryRequestor = createRequestor({
extensions: [
idempotent(),
cache({
duration: 30000, // 缓存 30 秒
persist: true // 持久化
})
]
})
// 3. 创建数据变更请求器(带重试)
export const mutationRequestor = createRequestor({
extensions: [
retry({
retries: 3,
delay: ({ attempt }) => Math.pow(2, attempt) * 200,
retryCondition: ({ lastResponse }) => {
// 只在网络错误或 5xx 时重试
const status = lastResponse?.response?.status
return !status || (status >= 500 && status < 600)
}
})
]
})
// 4. 使用
async function fetchUserProfile(userId: number) {
return queryRequestor.get(`/users/${userId}`)
}
async function updateUserProfile(userId: number, data: any) {
return mutationRequestor.put(`/users/${userId}`, data)
}示例 2:批量文件上传
import { createRequestor, concurrent, retry } from '@net-vert/core'
const uploadRequestor = createRequestor({
extensions: [
concurrent({ parallelCount: 3 }), // 同时最多 3 个上传
retry({ retries: 2, delay: 1000 }) // 失败重试 2 次
]
})
async function uploadFiles(files: File[]) {
const tasks = files.map(file => {
const formData = new FormData()
formData.append('file', file)
return uploadRequestor.post('/upload', formData)
})
return Promise.all(tasks)
}示例 3:React Suspense 集成
import { inject, createRequestor, sync } from '@net-vert/core'
import { Suspense } from 'react'
// 注入请求器
inject(config => fetch(config.url).then(res => res.json()))
// 创建 Suspense 风格的请求器
const suspenseRequestor = createRequestor({
extensions: [sync()]
})
// API 封装
const api = {
getUser: (id: number) => suspenseRequestor.get(`/api/users/${id}`),
getPosts: () => suspenseRequestor.get('/api/posts')
}
// 组件
function UserProfile({ userId }: { userId: number }) {
const user = api.getUser(userId) // 首次会触发 Suspense
return <div>{user.name}</div>
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId={1} />
</Suspense>
)
}🔧 高级用法
自定义中间件
你可以编写自己的中间件来扩展功能:
import { createRequestor, type Middleware } from '@net-vert/core'
// 自定义日志中间件
const loggerMiddleware: Middleware = async ({ config, next }) => {
console.log('Request:', config.method, config.url)
const startTime = Date.now()
try {
const result = await next()
console.log('Success:', Date.now() - startTime, 'ms')
return result
} catch (error) {
console.error('Error:', error)
throw error
}
}
// 使用自定义中间件
const requestor = createRequestor({
extensions: [loggerMiddleware]
})⚠️ 重要提示:如果你的自定义中间件需要拦截所有请求(如日志记录、鉴权检查等),必须将其放在中间件数组的最前面(
sync除外)。否则,被前置中间件(如cache)拦截的请求不会经过你的自定义中间件。详见 中间件顺序规则。
动态切换请求器
import { inject, useRequestor } from '@net-vert/core'
// 注入多个请求器
inject(axiosAdapter, 'axios')
inject(fetchAdapter, 'fetch')
// 动态选择
function getRequestor(type: 'axios' | 'fetch') {
return useRequestor(type)
}
// 使用
const requestor = getRequestor('axios')
requestor.get('/api/data')🧪 测试支持
轻松进行单元测试:
import { inject, createRequestor, cache } from '@net-vert/core'
import { vi } from 'vitest'
describe('API Tests', () => {
it('should cache requests', async () => {
// 创建 mock 请求器
const mockRequestor = vi.fn(async (config) => ({
code: 200,
data: { url: config.url }
}))
inject(mockRequestor)
const requestor = createRequestor({
extensions: [cache({ duration: 5000 })]
})
// 发起两次相同请求
await requestor.get('/api/test')
await requestor.get('/api/test')
// 验证只调用了一次
expect(mockRequestor).toHaveBeenCalledTimes(1)
})
})📤 项目信息
- 仓库地址: https://github.com/yvygyyth/net-vert
- 问题反馈: GitHub Issues
- 许可证: MIT
- 作者: yuzinan [email protected]
💡 设计理念
- 依赖倒置 - 网络层完全解耦,未来可自由切换底层实现
- 组合优于继承 - 通过中间件组合实现复杂功能
- 渐进式增强 - 零配置可用,按需添加能力
- 类型安全 - 完整的 TypeScript 支持
- 轻量纯粹 - 核心代码极简,扩展独立管理
🤝 贡献
欢迎提交 Issue 和 Pull Request!
📝 更新日志
v1.0.0 (2024-11)
- 🎉 正式版本发布
- ✨ 完整的中间件系统
- ✨ 支持缓存、幂等、重试、并发控制、同步模式
- ✨ 完整的 TypeScript 类型支持
- ✨ 支持多实例管理
- 📚 完善的文档和示例
如有任何问题或建议,欢迎在 GitHub 上联系我们!
