重复网络请求限制

80 阅读2分钟

重复网络请求限制

1.UI控制

  1. 添加loading的遮罩层 ,限制后续用户交互.
  2. 在按钮上触发后,添加disable,限制后续用户交互.

2.js 节流方式控制同一个时间的方法触发频次

重复的事件通过节流方式,只触发第一次,后续一定时间内都不响应(通过固定时间间隔限制)

//节流函数
function throttle(fn,delay) { 
    let last = 0;
    return (...args) => {
       let now = Date.now();//now的时间单位是 毫微秒 和 settimeout的delay是一致
       console.log("now",now,"last",last)
       if(now > last + delay ){
          last = now;
          fn.apply(this,args)//立刻执行原来定义的函数+ 参数
       }
    }
} 

function hello(info) {
   console.log("hello jason",info)
}

const testFn = throttle(hello,300) 
testFn("11")
testFn("22")
testFn("33")
setTimeout(() => { 
    testFn("44")
}, 2000);

3. 参数一样,上一次被取消,当前继续执行

request阶段

  1. 先判断map是否有一样的key。有则取出并取消
  2. 继续当前网络请求,并把当前加入map

response阶段

  1. 把当前请求的key在map移除
import axios from 'axios';
import qs from 'qs'; // 记得引入qs

const CancelToken = axios.CancelToken;
const requestMap = new Map<string, Function>(); // 保存 cancel 函数

// 请求拦截器
axios.interceptors.request.use(
  config => {
    const keyString = qs.stringify(
      Object.assign({}, { url: config.url, method: config.method }, config.data)
    );

    // 如果存在相同的请求,则取消上一次请求
    if (requestMap.has(keyString)) {
      const cancel = requestMap.get(keyString);
      if (cancel) {
        cancel('取消上一次请求');
      }
      requestMap.delete(keyString); // 取消后清理掉
    }

    // 创建新的 cancelToken,并存储 cancel 函数
    config.cancelToken = new CancelToken(cancel => {
      requestMap.set(keyString, cancel);
    });

    // 保存key到config,后续响应拦截器用
    (config as any)._keyString = keyString;

    // post、put、delete请求,body参数要序列化
    if (config.method === 'post' || config.method === 'put' || config.method === 'delete') {
      config.data = qs.stringify(config.data);
    }

    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 响应拦截器
axios.interceptors.response.use(
  res => {
    const config = res.config as any;
    if (config._keyString) {
      requestMap.delete(config._keyString); // 请求成功后,清理 map
    }
    return res;
  },
  error => {
    const config = error.config as any;
    if (config && config._keyString) {
      requestMap.delete(config._keyString); // 出错也要清理
    }
    return Promise.reject(error);
  }
);

4.统一汇总某一刻所有请求,参数一致的网络请求的不再发起,取消当前请求

request阶段

  1. 判断当前网络请求key是否在里面,在则直接取消当前请求,当前请求会被劫持
  2. 不在的话加入map,然后继续执行

response阶段

  1. 把当前请求的key在map移除
import axios from 'axios';
 
const CancelToken = axios.CancelToken;
const requestMap = new Map();
 
// 请求前置拦截器
Axios.interceptors.request.use(
    config => {
        // 防重复提交
         const keyString = qs.stringify(Object.assign({}, { url: config.url, method: config.method }, config.data));
        if (requestMap.get(keyString)) {
            // 取消当前请求
            config.cancelToken = new CancelToken((cancel) => {
                cancel('取消请求');
            }); 
        }
        requestMap.set(keyString, true);
        Object.assign(config, { _keyString: keyString });
 
        if (config.method === 'post' || config.method === 'put' || config.method === 'delete') {
            // 序列化
            config.data = qs.stringify(config.data);
        }
        return config;
    },
    error => {
        return Promise.reject(error);
    }
);
 
// 返回响应拦截器
Axios.interceptors.response.use(
    (res: any) => {
        // 重置requestMap
        const config: any = res.config;
        requestMap.set(config._keyString, false);
        if (res.status === 200) {
            return res;
        }
     },
    error => {
        return Promise.reject({ error })
    }
);

取消第二种方式

if (requestMap.get(keyString)) { 
// 方法2 直接手动 reject 一个 Cancel 错误 
    return Promise.reject(new axios.Cancel(`Duplicate request canceled: ${keyString}`)); 
}

常见的前端网络库

XMLHttpRequest

早期的底层网络请求库, jQuery 的 ajax 和 axios 都是基于他做封装

fetch

新的底层网络库,支持 promise ,性能更优

终止网络的方式

XMLHttpRequest.abort()

// 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest();
// 请求地址
const url = "https://api.github.com";
// 初始化请求
xhr.open("GET", url, true);
// 发送请求
xhr.send();
// 监听取消请求
xhr.addEventListener("abort", function () {
  console.log("请求被abort()取消了");
});
// 定时器模拟取消请求
setTimeout(() => {
  // 取消请求
  xhr.abort();
  // 取消请求之后的状态status
  console.log("abort()之后的xhr.status---", xhr.status);
  // 取消请求之后的状态readyState
  console.log("abort()之后的xhr.readyState---", xhr.readyState);
}, 100);

axios 的 AbortController

// 以vue项目中使用axios为例

// 创建请求控制器
this.controller = new AbortController();
// 第一种方法:绑定事件处理程序
this.controller.signal.addEventListener("abort", () => {
  console.log("请求已终止,触发了onabort事件");
  // 进行后续处理
});

// 第二种方法:try...catch
try {
  // 发送文件上传请求
  const res = await this.$axios.post('https://api.github.com', {}, {
    timeout: 0, // 设置超时时间为 0/null 表示永不超时
    signal: this.controller.signal, // 绑定取消请求的信号量
  });
} catch (error) {
  console.log("终止请求时catch的error---", error);
  // 判断是否为取消上传
  if (error.message == "canceled") {
    // 进行后续处理
  }
}
// 终止请求
this.controller.abort();

axios 的 CancelToken

新写法axios.cancelToken.source()

import axios from "axios";
this.source = axios.CancelToken.source();
console.log("初始声明的请求令牌---", this.source);

// 第二种方法:try...catch
try {
  // 发送文件上传请求
  const res = await this.$axios.post(
    "https://api.github.com",
    {},
    {
      timeout: 0, // 设置超时时间为 0/null 表示永不超时
      cancelToken: this.source.token, // 绑定取消请求的令牌
    }
  );
} catch (error) {
  console.log("终止请求时catch的error---", error);
  // 判断是否为取消上传
  if (error.message == "自定义取消请求的message") {
    // 进行后续处理
  }
}

// 终止请求
this.source.cancel("自定义取消请求的message");
console.log("取消请求后的请求令牌---", this.source);

旧写法

const CancelToken = axios.CancelToken;
let cancel;

// 1. 发请求时,自己new一个CancelToken
axios.get('/some/api', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c; // 将 cancel 函数暴露出去
  })
});

// 2. 需要取消时,调用 cancel()
cancel('取消请求');

fetch 的 AbortController

使用方法跟axios类似

const controller = new AbortController();
const { signal } = controller; 
fetch("https://api.github.com/", {
  signal,
}).then((response) => {
    console.log(`Request 2 is complete! responce:`, response);
  }).catch((e) => {
    console.warn(`Fetch 2 error: ${e.message}`);
  });

// abort request
controller.abort();