feat(useRequest): 请求取消功能,有效避免内存泄漏和不必要的资源消耗,尤其适用于长时间运行的请求或组件卸载场景。

This commit is contained in:
liaochunxin
2025-09-23 09:58:25 +08:00
parent 674ea563f2
commit 7923aa7423
4 changed files with 55 additions and 17 deletions

View File

@@ -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 }
}

View File

@@ -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! }
}
/**

View File

@@ -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

View File

@@ -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>