@kinngyo/wx-request
v0.0.6
Published
基于wx.request封装的微信小程序网络请求,支持请求拦截、响应拦截、节流功能
Readme
@kinngyo/wx-request
基于 wx.request 封装的微信小程序网络请求工具,提供实例配置、拦截器、请求节流、取消请求和 TypeScript 类型支持。
特性
- 基于微信小程序原生
wx.request - 支持
baseURL、header、timeout等默认配置 - 支持请求拦截器和响应拦截器
- 支持业务自定义参数,方便控制 loading、toast、鉴权等逻辑
- 支持请求节流,避免短时间重复提交
- 支持
AbortController和requestTask.abort()取消请求 - 支持
pureRequest跳过拦截器和节流 - TypeScript 友好
安装
npm i @kinngyo/wx-request使用环境
@kinngyo/wx-request 面向微信小程序环境,依赖全局 wx.request。
| 环境 | 支持情况 | | ---------- | ---------- | | 微信小程序 | 支持 | | TypeScript | 支持 | | 浏览器 | 非目标环境 | | Node.js | 非目标环境 |
目录
快速开始
import Request from '@kinngyo/wx-request'
const request = new Request({
baseURL: 'https://api.example.com',
})
const result = await request.request({
url: '/user',
method: 'GET',
})
console.log(result.response?.data)请求成功时,结果会放在 result.response 中;请求失败时会进入 catch,错误信息中会带上本次请求配置。
try {
const result = await request.request({
url: '/user',
})
console.log(result.response?.data)
} catch (error) {
console.log(error)
}url 支持相对地址和完整地址。相对地址会和 baseURL 拼接,完整地址会直接使用。
await request.request({ url: '/user' })
await request.request({ url: 'https://other.example.com/user' })推荐项目封装
实际项目中建议单独创建一个请求模块,例如 utils/request.ts,统一处理基础地址、token、错误提示和快捷方法。
import Request, {
Interceptor,
type CustomRequestConfig,
type CustomRequestResult,
type RequestData,
type RequestInputConfig,
type RequestResult,
} from '@kinngyo/wx-request'
interface AppConfig {
showErrorToast?: boolean
needToken?: boolean
}
type AppRequestConfig<T extends RequestData = RequestData> =
CustomRequestConfig<AppConfig, T>
type AppRequestResult<T extends RequestData = RequestData> =
CustomRequestResult<AppConfig, T>
type ApiConfig<T extends RequestData = RequestData> = Omit<
RequestInputConfig<T> & Partial<AppConfig>,
'url' | 'method' | 'data'
>
const request = new Request<AppConfig>({
baseURL: 'https://api.example.com',
timeout: 10000,
showErrorToast: true,
needToken: true,
})
const requestInterceptor = new Interceptor<AppRequestConfig>()
requestInterceptor.add({
onFulfilled: config => {
if (!config.needToken) {
return config
}
return {
...config,
header: {
...(config.header ?? {}),
Authorization: `Bearer ${wx.getStorageSync('token')}`,
},
}
},
})
const responseInterceptor = new Interceptor<AppRequestResult>()
responseInterceptor.add({
onFulfilled: result => result,
onRejected: error => {
if (error.config?.showErrorToast) {
wx.showToast({
title: '请求失败',
icon: 'none',
})
}
return Promise.reject(error)
},
})
request.interceptors.use(requestInterceptor, responseInterceptor)
/**
* 发送 GET 请求
* @param url 请求地址
* @param data 请求参数
* @param config 请求配置
* @returns 请求结果
*/
export function get<T extends RequestData = RequestData>(
url: string,
data?: T,
config: ApiConfig<T> = {},
): Promise<RequestResult<T>> {
return request.request<T>({
...config,
url,
data,
method: 'GET',
})
}
/**
* 发送 POST 请求
* @param url 请求地址
* @param data 请求数据
* @param config 请求配置
* @returns 请求结果
*/
export function post<T extends RequestData = RequestData>(
url: string,
data?: T,
config: ApiConfig<T> = {},
): Promise<RequestResult<T>> {
return request.request<T>({
...config,
url,
data,
method: 'POST',
})
}
export default request业务页面中直接使用封装后的方法。
import { get, post } from '@/utils/request'
const userResult = await get('/user', { id: 1 })
const saveResult = await post('/user/save', { name: 'kinngyo' })
console.log(userResult.response?.data)
console.log(saveResult.response?.data)默认配置
构造函数接收默认请求配置。调用 request 时,单次请求配置会覆盖默认配置,header 内部也遵循这个规则。
import Request from '@kinngyo/wx-request'
const request = new Request({
baseURL: 'https://api.example.com',
header: {
token: 'default-token',
},
})
await request.request({
url: '/profile',
header: {
token: 'page-token',
traceId: 'request-id',
},
})最终请求地址为 https://api.example.com/profile,并且 header.token 会使用 page-token。
自定义参数
自定义参数会和请求配置一起合并,并保留在最终 config 中,适合控制 loading、toast、鉴权、数据格式化等业务行为。
import Request from '@kinngyo/wx-request'
interface AppConfig {
showLoading?: boolean
showErrorToast?: boolean
needToken?: boolean
}
const request = new Request<AppConfig>({
baseURL: 'https://api.example.com',
showLoading: true,
showErrorToast: true,
needToken: true,
})
const result = await request.request({
url: '/user',
showLoading: false,
})
console.log(result.config.showLoading)拦截器
Interceptor 用于定义可复用的拦截器组。请求拦截器适合处理 token、loading、参数转换;响应拦截器适合处理错误提示、登录失效、响应格式化。
import Request, {
Interceptor,
type CustomRequestConfig,
type CustomRequestResult,
} from '@kinngyo/wx-request'
interface AppConfig {
showLoading?: boolean
}
type AppRequestConfig = CustomRequestConfig<AppConfig>
type AppRequestResult = CustomRequestResult<AppConfig>
const request = new Request<AppConfig>({
baseURL: 'https://api.example.com',
showLoading: true,
})
const requestInterceptor = new Interceptor<AppRequestConfig>()
requestInterceptor.add({
onFulfilled: config => {
if (config.showLoading) {
wx.showLoading({ title: '加载中' })
}
return {
...config,
header: {
...(config.header ?? {}),
token: wx.getStorageSync('token'),
},
}
},
onRejected: error => Promise.reject(error),
})
const responseInterceptor = new Interceptor<AppRequestResult>()
responseInterceptor.add({
onFulfilled: result => {
wx.hideLoading()
return result
},
onRejected: error => {
wx.hideLoading()
return Promise.reject(error)
},
})
request.interceptors.request.use(requestInterceptor)
request.interceptors.response.use(responseInterceptor)也可以用快捷方式同时接入请求和响应拦截器。
request.interceptors.use(requestInterceptor, responseInterceptor)同一个拦截器组可以复用到多个请求实例。
const userRequest = new Request<AppConfig>({
baseURL: 'https://user.example.com',
})
const orderRequest = new Request<AppConfig>({
baseURL: 'https://order.example.com',
})
userRequest.interceptors.request.use(requestInterceptor)
orderRequest.interceptors.request.use(requestInterceptor)请求节流
Throttle 会按请求方法、完整地址和请求数据生成唯一 key。在同一时间窗口内超过最大次数时,不会继续调用 wx.request。
import Request, { Throttle } from '@kinngyo/wx-request'
const throttle = new Throttle({
timeWindow: 500,
maxCount: 3,
})
const request = new Request({
baseURL: 'https://api.example.com',
throttle,
})
await request.request({
url: '/submit',
method: 'POST',
data: { name: 'kinngyo' },
})默认 key 由 method + url + JSON.stringify(data) 生成。需要按业务字段节流时,可以自定义 getKey。
const throttle = new Throttle({
timeWindow: 1000,
maxCount: 1,
getKey(config) {
return `${config.method ?? 'GET'}:${config.url}:${config.data.orderId}`
},
})触发节流后默认返回 rejected Promise,也可以通过 response 自定义行为。
const throttle = new Throttle({
timeWindow: 500,
maxCount: 3,
response(config) {
return (_resolve, reject) => {
reject({
error: '频繁操作,请稍后再试!',
config,
})
}
},
})单次请求也可以覆盖默认节流器。
await request.request({
url: '/submit',
method: 'POST',
throttle: new Throttle({
timeWindow: 1000,
maxCount: 1,
}),
})需要重置节流状态时,可以清理指定请求 key 或全部记录。
throttle.clear()取消请求
库内提供轻量版 AbortController,底层会桥接到小程序原生 RequestTask.abort()。
import Request, { AbortController } from '@kinngyo/wx-request'
const request = new Request({
baseURL: 'https://api.example.com',
})
const controller = new AbortController()
request.request({
url: '/long-task',
signal: controller.signal,
})
controller.abort()页面卸载时取消请求是常见用法。
import Request, { AbortController } from '@kinngyo/wx-request'
const request = new Request({
baseURL: 'https://api.example.com',
})
Page({
controller: new AbortController(),
onLoad() {
request.request({
url: '/page-data',
signal: this.controller.signal,
})
},
onUnload() {
this.controller.abort()
},
})如果运行环境已经提供标准 AbortController,也可以直接传入原生 controller.signal。
获取 requestTask
通过 task 可以拿到 wx.request 返回的请求任务。
let task: WechatMiniprogram.RequestTask | undefined
request.request({
url: '/long-task',
task(requestTask) {
task = requestTask
},
})
task?.abort()signal 和 task 可以同时使用。
import { AbortController } from '@kinngyo/wx-request'
const controller = new AbortController()
let task: WechatMiniprogram.RequestTask | undefined
request.request({
url: '/long-task',
signal: controller.signal,
task(requestTask) {
task = requestTask
},
})
controller.abort()
task?.abort()pureRequest
pureRequest 会复用默认配置并直接调用 wx.request,不会执行拦截器,也不会触发节流逻辑,但仍然支持 signal 和 task。
const result = await request.pureRequest({
url: '/health',
})
console.log(result.response?.statusCode)API
Request
const request = new Request(defaultConfig)| 参数 | 类型 | 必填 | 说明 |
| ------------- | ----------------------------------- | ---- | ------------ |
| defaultConfig | RequestDefaultConfig & Partial<T> | 否 | 默认请求配置 |
request.request
request.request(config)| 参数 | 类型 | 必填 | 说明 |
| ------ | --------------------------------- | ---- | ------------ |
| config | RequestInputConfig & Partial<T> | 是 | 单次请求配置 |
返回 Promise<RequestResult>。
request.pureRequest
request.pureRequest(config)| 参数 | 类型 | 必填 | 说明 |
| ------ | --------------------------------- | ---- | ------------ |
| config | RequestInputConfig & Partial<T> | 是 | 单次请求配置 |
返回 Promise<RequestResult>。不会执行拦截器和请求节流。
request.interceptors
request.interceptors.request.use(requestInterceptor)
request.interceptors.response.use(responseInterceptor)
request.interceptors.use(requestInterceptor, responseInterceptor)| 方法 | 说明 |
| -------------- | ------------------------------ |
| request.use | 添加请求拦截器 |
| response.use | 添加响应拦截器 |
| use | 同时添加请求拦截器和响应拦截器 |
Interceptor
const interceptor = new Interceptor()
interceptor.add(plugin)| 参数 | 类型 | 必填 | 说明 |
| ------ | ---------------------- | ---- | ---------- |
| plugin | InterceptorPlugin<T> | 是 | 拦截器插件 |
Throttle
const throttle = new Throttle(config)| 参数 | 类型 | 必填 | 默认值 | 说明 |
| ---------- | ----------------------------------- | ---- | ---------------- | ---------------------------- |
| timeWindow | number | 否 | 500 | 节流时间窗口,单位 ms |
| maxCount | number | 否 | 3 | 时间窗口内允许的最大请求次数 |
| response | ResponseCallback | 否 | rejected Promise | 触发节流后的响应行为 |
| getKey | (config: RequestConfig) => string | 否 | 内置 key 规则 | 自定义请求唯一标识 |
AbortController
const controller = new AbortController()
controller.abort()| 属性/方法 | 说明 |
| --------- | ------------ |
| signal | 取消请求信号 |
| abort() | 取消请求 |
类型说明
请求配置
| 参数 | 类型 | 必填 | 说明 |
| -------- | ------------------------------------------- | ---- | ------------ |
| url | string | 是 | 请求地址 |
| baseURL | string | 否 | 请求基础地址 |
| method | WechatMiniprogram.RequestOption["method"] | 否 | 请求方法 |
| data | RequestData | 否 | 请求数据 |
| header | WechatMiniprogram.IAnyObject | 否 | 请求头 |
| timeout | number | 否 | 超时时间 |
| throttle | Throttle | 否 | 请求节流器 |
| signal | AbortSignalLike | 否 | 取消请求信号 |
| task | (requestTask: RequestTask) => void | 否 | 获取请求任务 |
除上表字段外,也支持 wx.request 的其他原生参数。
请求结果
interface RequestResult<T, U> {
config: U
response?: WechatMiniprogram.RequestSuccessCallbackResult<T>
error?: WechatMiniprogram.RequestFailCallbackErr
}| 字段 | 说明 | | -------- | -------------------- | | config | 最终请求配置 | | response | 请求成功时的响应结果 | | error | 请求失败时的错误结果 |
FAQ
为什么不是直接返回 data?
为了保留微信小程序完整响应信息,返回值中会保留 statusCode、header、cookies、data 和最终请求配置。业务中可以通过 result.response?.data 取数据。
request 和 pureRequest 有什么区别?
request 会执行请求拦截器、请求节流、真实请求和响应拦截器。pureRequest 只执行配置合并、地址拼接和真实请求。
请求节流为什么没有生效?
常见原因是每次请求的 data 中包含 timestamp、随机数、traceId 等变化字段,导致生成的 key 不一致。此时建议使用 getKey 自定义稳定 key。
如何统一处理 token 失效?
建议在响应拦截器中判断业务状态码,清理登录态并跳转登录页。
responseInterceptor.add({
onFulfilled: result => {
if (result.response?.data?.code === 401) {
wx.removeStorageSync('token')
wx.navigateTo({ url: '/pages/login/index' })
}
return result
},
})如何在页面卸载时取消请求?
创建 AbortController,请求时传入 signal,在页面 onUnload 中调用 abort()。
导出内容
import Request, {
AbortController,
AbortSignal,
Interceptor,
Throttle,
buildFullPath,
combineURLs,
createRequestKey,
isAbsoluteURL,
resolveRequestConfig,
} from '@kinngyo/wx-request'
import type {
AbortSignalLike,
CustomRequestConfig,
CustomRequestResult,
InterceptorCallback,
InterceptorConfig,
InterceptorPlugin,
Optional,
RequestConfig,
RequestResult,
RequestTask,
ResponseCallback,
ThrottleConfig,
} from '@kinngyo/wx-request'核心逻辑
核心设计参考 axios 的请求模型:先合并配置,再按 Promise 链执行请求拦截器、真实请求和响应拦截器。底层请求能力来自微信小程序的 wx.request,取消请求会落到原生 RequestTask.abort()。
一次 request() 请求会按下面顺序执行:
- 合并默认配置和本次请求配置
- 执行请求拦截器
- 拼接
baseURL和url - 判断是否触发请求节流
- 调用
wx.request - 绑定
signal和requestTask - 执行响应拦截器
对应到内部实现:
resolveRequestConfig:合并默认配置和本次请求配置Interceptor:维护请求/响应拦截器组dispatchRequest:处理完整地址、节流、取消请求和wx.requestrequest:组装完整 Promise 链pureRequest:跳过拦截器和节流,直接进入dispatchRequest(false)
