feat(useRequest): 请求取消功能,有效避免内存泄漏和不必要的资源消耗,尤其适用于长时间运行的请求或组件卸载场景。
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { HttpRequestResult } from '@/http/types'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface IUseRequestOptions<T> {
|
||||
@@ -13,6 +14,7 @@ interface IUseRequestReturn<T, P = undefined> {
|
||||
error: Ref<boolean | Error>
|
||||
data: Ref<T | undefined>
|
||||
run: (args?: P) => Promise<T | undefined>
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,15 +26,19 @@ interface IUseRequestReturn<T, P = undefined> {
|
||||
* @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。
|
||||
*/
|
||||
export default function useRequest<T, P = undefined>(
|
||||
func: (args?: P) => Promise<T>,
|
||||
func: (args?: P) => HttpRequestResult<T>,
|
||||
options: IUseRequestOptions<T> = { immediate: false },
|
||||
): IUseRequestReturn<T, P> {
|
||||
const loading = ref(false)
|
||||
const error = ref(false)
|
||||
const error = ref<boolean | Error>(false)
|
||||
const data = ref<T | undefined>(options.initialData) as Ref<T | undefined>
|
||||
let requestTask: UniApp.RequestTask | undefined
|
||||
|
||||
const run = async (args?: P) => {
|
||||
loading.value = true
|
||||
return func(args)
|
||||
const { promise, requestTask: task } = func(args)
|
||||
requestTask = task // Store the requestTask
|
||||
return promise
|
||||
.then((res) => {
|
||||
data.value = res
|
||||
error.value = false
|
||||
@@ -47,8 +53,16 @@ export default function useRequest<T, P = undefined>(
|
||||
})
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
if (requestTask) {
|
||||
requestTask.abort()
|
||||
loading.value = false // Reset loading state on cancel
|
||||
error.value = new Error('Request cancelled') // Set a specific error for cancellation
|
||||
}
|
||||
}
|
||||
|
||||
if (options.immediate) {
|
||||
(run as (args?: P) => Promise<T | undefined>)({} as P)
|
||||
}
|
||||
return { loading, error, data, run }
|
||||
return { loading, error, data, run, cancel }
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import { ResultEnum } from './tools/enum'
|
||||
|
||||
// 刷新 token 状态管理
|
||||
let refreshing = false // 防止重复刷新 token 标识
|
||||
let taskQueue: (() => void)[] = [] // 刷新 token 请求队列
|
||||
let taskQueue: { resolve: (value: any) => void, reject: (reason?: any) => void, options: CustomRequestOptions }[] = [] as { resolve: (value: any) => void, reject: (reason?: any) => void, options: CustomRequestOptions }[] // 刷新 token 请求队列
|
||||
|
||||
export function http<T>(options: CustomRequestOptions) {
|
||||
// 1. 返回 Promise 对象
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
uni.request({
|
||||
let requestTask: UniApp.RequestTask | undefined
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
requestTask = uni.request({
|
||||
...options,
|
||||
dataType: 'json',
|
||||
// #ifndef MP-WEIXIN
|
||||
@@ -44,9 +44,7 @@ export function http<T>(options: CustomRequestOptions) {
|
||||
const { refreshToken } = tokenStore.tokenInfo as IDoubleTokenRes || {}
|
||||
// token 失效的,且有刷新 token 的,才放到请求队列里
|
||||
if ((res.statusCode === 401 || resData.code === 401) && refreshToken) {
|
||||
taskQueue.push(() => {
|
||||
resolve(http<T>(options))
|
||||
})
|
||||
taskQueue.push({ resolve, reject, options })
|
||||
}
|
||||
// 如果有 refreshToken 且未在刷新中,发起刷新 token 请求
|
||||
if ((res.statusCode === 401 || resData.code === 401) && refreshToken && !refreshing) {
|
||||
@@ -65,7 +63,9 @@ export function http<T>(options: CustomRequestOptions) {
|
||||
})
|
||||
})
|
||||
// 将任务队列的所有任务重新请求
|
||||
taskQueue.forEach(task => task())
|
||||
taskQueue.forEach((task) => {
|
||||
http<T>(task.options).promise.then(task.resolve, task.reject)
|
||||
})
|
||||
}
|
||||
catch (refreshErr) {
|
||||
console.error('刷新 token 失败:', refreshErr)
|
||||
@@ -112,6 +112,7 @@ export function http<T>(options: CustomRequestOptions) {
|
||||
},
|
||||
})
|
||||
})
|
||||
return { promise, requestTask: requestTask! }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,11 @@ export type CustomRequestOptions = UniApp.RequestOptions & {
|
||||
hideErrorToast?: boolean
|
||||
} & IUniUploadFileOptions // 添加uni.uploadFile参数类型
|
||||
|
||||
export interface HttpRequestResult<T> {
|
||||
promise: Promise<T>
|
||||
requestTask: UniApp.RequestTask
|
||||
}
|
||||
|
||||
// 通用响应格式
|
||||
export interface IResponse<T = any> {
|
||||
code: number | string
|
||||
|
||||
@@ -8,10 +8,14 @@ import { getFooAPI } from '@/api/foo'
|
||||
// }
|
||||
const initialData = undefined
|
||||
|
||||
const { loading, error, data, run } = useRequest<IFooItem>(() => getFooAPI('菲鸽'), {
|
||||
const { loading, error, data, run, cancel } = useRequest<IFooItem>(() => getFooAPI('菲鸽'), {
|
||||
immediate: true,
|
||||
initialData,
|
||||
})
|
||||
function reqFooAPI() {
|
||||
run()
|
||||
cancel()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
data.value = initialData
|
||||
@@ -31,17 +35,31 @@ function reset() {
|
||||
<button type="primary" size="mini" class="w-160px" @click="run">
|
||||
发送请求
|
||||
</button>
|
||||
<button type="default" size="mini" class="ml-4 w-160px" @click="reqFooAPI">
|
||||
发送请求立即取消
|
||||
</button>
|
||||
<button type="default" size="mini" class="ml-4 w-160px" :disabled="!loading" @click="cancel">
|
||||
取消请求
|
||||
</button>
|
||||
</view>
|
||||
<view class="h-16">
|
||||
<view v-if="loading">
|
||||
loading...
|
||||
</view>
|
||||
<block v-else>
|
||||
<view class="text-xl">
|
||||
请求数据如下
|
||||
<view v-if="error instanceof Error" class="text-red leading-8">
|
||||
错误: {{ error.message }}
|
||||
</view>
|
||||
<view class="text-green leading-8">
|
||||
{{ JSON.stringify(data) }}
|
||||
<view v-else-if="error" class="text-red leading-8">
|
||||
错误: 未知错误
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="text-xl">
|
||||
请求数据如下
|
||||
</view>
|
||||
<view class="text-green leading-8">
|
||||
{{ JSON.stringify(data) }}
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
Reference in New Issue
Block a user