性能优化之Axios接口防抖

52 阅读2分钟

背景

项目中经常会出现一种业务场景:一个‘输入框’、一个‘搜索按钮’,点击按钮,按输入字符串进行搜索。相信很多人都可能存在一种情况——重复点击按钮进行搜索,并且使用方式可能会是以下代码的形式。

async search() {
    this.list = await this.fetchData(queryStr);
}

在多次点击按钮时,会存在一个性能问题:返回数据没变,但是又重新渲染页面了。

方案

当然,对于这种比较常见的性能问题,有多种方案去处理。这里,我主要给出的是Axios层面封装防抖函数,来处理这一类问题。采用这种方案,是因为Axios的封装可以针对项目的所有请求,而不单单是此场景下的单个问题。

这里额外说一点就是,浏览器cancel请求的原理,其实是浏览器发出了请求,服务器也处理返回了数据,但是浏览器监听到cancel消息后,放弃了对该接口的监听,所以也不会处理数据。

另外,主动cancel请求会被try/catch所捕获,如果你项目封装catch的时候有toast错误的话,要注意这一点。

代码其实也比较简单,不过多赘述,直接附上完整代码。

完整代码

// cancelConfig.ts  
import qs from 'qs';  
import { AxiosRequestConfigCancelTokenCancelTokenStaticCancelTokenSource } from 'axios';  

export interface AxiosCancelConfigParams extends AxiosRequestConfig {  
    source?: CancelTokenSource;  
}

export default class AxiosCancelConfig {  
    cancelKeyMapMap<string, CancelTokenSource>;  
    constructor() {  
        this.cancelKeyMap = new Map();  
    }
    
    getKey(configAxiosCancelConfigParams): string {  
        const { method = 'GET', url, data = {}, params = {} } = config;  
        return `${method}${url}${qs.stringify(data)}${qs.stringify(params)}`;  
    } 
    
    add(config: AxiosCancelConfigParams, source: CancelTokenSource) {  
        const key = this.getKey(config);  
        if (config.source) {  
            if (!this.cancelKeyMap.has(key)) {  
                this.cancelKeyMap.set(key, config.source);  
            }  
        } else {  
            config.cancelToken = source.token;  
            if (!this.cancelKeyMap.has(key)) {  
                this.cancelKeyMap.set(key, source);  
            }  
        }  
    }  
   
    cancel(config: AxiosCancelConfigParams) {  
        const key = this.getKey(config);  
        if (this.cancelKeyMap.has(key)) {  
            const source = this.cancelKeyMap.get(key);  
            source?.cancel();  
            this.remove(config);  
        }  
    }  
    remove(config: AxiosCancelConfigParams) {  
        const key = this.getKey(config);  
        if (this.cancelKeyMap.has(key)) {  
            this.cancelKeyMap.delete(key);  
        }  
    }  
}  

// config.js

// 请求拦截器:如果存在source或者cancelToken,则认为该接口自行处理cancel行为  ,否则默认进行防抖;
axiosCancelConfigInstance = new AxiosCancelConfig;

if (!config.source && !config.cancelToken) {  
    const source = axios.CancelToken.source();  
    axiosCancelConfigInstance.cancel(config);  
    axiosCancelConfigInstance.add(config, source);  
}  
      
 // 响应拦截器:
 try {  
    const axiosRes = await service(options);  
    await axiosCancelConfigInstance.remove(axiosRes.config);  
    const res = axiosRes.data;  
    .....  
} catch (error) {  
    //...  
    const __CANCEL__ = (error as any).__proto__.__CANCEL__ || false;  
    if (__CANCEL__) {  
        return Promise.reject();  
    }  
    errorToast((error as any).message);  
    return Promise.reject();  
}