跳到主要内容

请求相关

取消请求相关​

  • AbortController
    • signal 属性
      • signal 是 AbortController 提供的一个对象,用于传递取消信号
      • 实现对异步任务的取消控制
    • abort方法
      • 触发 abort 方法会将 signal.aborted 设置为 true
      • 同时会触发 signal 上的 abort 事件。

请求竞态​

  • 请求竞态在项目中,比较常见的一个问题,在异步请求或者并发请求的时候,需要考虑的问题
    • 请求竞态:比如多个请求去获取数据的时候,由于网络问题或者其他的不确定原因,可能会导致先发出去的请求不一定先响应,导致数据的不一致性,资源争夺问题

场景:

  • 下拉搜索:发起了多次请求,而上一次请求还没响应,而下个请求就已经响应完了,然后上一个请求响应之后,会覆盖下个请求,导致下个请求的数据被覆盖, 因此需要取消上一个请求未完成

封装请求方法hooks​

  • 集成try/catch
  • 集成AbortController,实现取消请求

usePromise​

js
import { get } from 'lodash-es';

type PromiseOption = {
successFn?: (result: any) => void;
errorFn?: (error: any) => void;
finallyFn?: () => void;
isAbort?: boolean; // 取消上一次请求
key?: string; // 请求的唯一key
};

// 设置signal, 必须设置key (请求key标识)
type PromiseType = Promise<any> | (({ signal }: { signal: AbortSignal }) => Promise<any>);

let globalAbortController: Record<string, AbortController | null> = {}; // 全局管理请求的上一个 AbortController

export async function usePromise(promise: PromiseType, option?: PromiseOption) {
const genKey = () => {
if (option?.key) {
return `${option.key}`;
}
return location.href;
};

const abortReq = () => {
const abort = get(globalAbortController, [genKey(), 'abort']);
if (abort) {
globalAbortController[genKey()]?.abort();
Reflect.deleteProperty(globalAbortController, genKey());
}
};

// onBeforeUnmount(() => {
// abortReq();
// });

if (option?.isAbort && option?.key && Reflect.has(globalAbortController, genKey())) {
abortReq();
}

if (option?.isAbort && option.key) {
const currentAbortController = new AbortController();
globalAbortController[genKey()] = currentAbortController;
}

try {
let result: any = null;
if (typeof promise === 'function') {
result = await promise({ signal: get(globalAbortController, [genKey(), 'signal']) });
} else {
result = await promise;
}
option?.successFn?.(result);
return [null, result];
} catch (error) {
console.log('error', error);
option?.errorFn?.(error);
return [error, null];
} finally {
option?.finallyFn?.();
const curKey = genKey();
if (option?.key && globalAbortController[curKey]) {
Reflect.deleteProperty(globalAbortController, curKey);
}
}
}

完善优化版本 usePromise​

可能出现的问题

  • 快速频繁请求时上一个请求未被取消
  • 可能导致AbortController请求冲突, 同一个key下, 多个请求下AbortController影响
  • 只想取消某一个具体请求,但由于 requestKey 共享了 AbortController,取消会波及到所有同 requestKey 的请求

解决:

  • 使用 Map 是为了在同一个 requestKey 下精确管理多个请求
  • 给每个请求,都创建新的一个AbortController防止冲突
  • 同 key 下管理多个请求, 支持批量取消
  • 使用requestId可以避免请求信息冲突,区分每个请求, 精准管理取消操作
  • 通过requestId 来避免冲突, 每个请求拥有独立的 AbortController,可以根据 requestId 精确取消某个特定的请求,而不会影响其他请求
ts
type PromiseOption = {
successFn?: (result: any) => void;
errorFn?: (error: any) => void;
finallyFn?: () => void;
isAbort?: boolean; // 是否取消上一次请求
key?: string; // 请求的唯一 key(逻辑区分)
};

type PromiseType = Promise<any> | (({ signal }: { signal: AbortSignal }) => Promise<any>);

/**
* 一个请求key可以管理多个请求, 支持批量取消
* 给每个请求,都创建新的一个AbortController,防止冲突
*/
let globalAbortController: Record<string, Map<string, AbortController>> = {}; // 每个逻辑 key 下可以有多个请求

export async function usePromise(promise: PromiseType, option?: PromiseOption) {
console.log('globalAbortController', globalAbortController);
const genKey = () => option?.key || location.href; // 生成逻辑 key
const requestKey = genKey();

if (!globalAbortController[requestKey]) {
globalAbortController[requestKey] = new Map();
}

// 生成唯一请求 ID
const requestId = `${Date.now()}-${Math.random()}`;

const abortReq = () => {
const requestMap = globalAbortController[requestKey];
if (requestMap) {
// 取消当前逻辑 key 下的所有请求
for (const [id, controller] of requestMap.entries()) {
controller?.abort();
requestMap.delete(id);
}
}
};

// 取消请求
if (option?.isAbort) {
abortReq();
}

const newController = new AbortController();
globalAbortController[requestKey].set(requestId, newController);

try {
let result: any = null;

if (typeof promise === 'function') {
result = await promise({ signal: newController.signal });
} else {
result = await promise;
}
option?.successFn?.(result);
return [null, result];
} catch (error) {
console.log('error', error);
option?.errorFn?.(error);
return [error, null];
} finally {
globalAbortController[requestKey].delete(requestId);
// 如果当前逻辑 key 下没有未完成请求,移除该逻辑 key
if (globalAbortController[requestKey].size === 0) {
Reflect.deleteProperty(globalAbortController, requestKey);
}
option?.finallyFn?.();
}
}