当前位置: 首页 > article >正文

重复请求取消(不发请求)

重复请求取消(不发请求)

axios 有自带的取消请求方式, 但是 在请求已发出时,取消只是状态取消, 其实请求已经发出,消耗了服务器资源(方式1)。本文探究的是 在调用请求A时, 在A未响应之前,所有的A重复请求都应该不发出,待A响应之后,将响应结果给到之前的所有A请求!(方式2,3)

基础代码

// request.js
export const request = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

// api/user.js  获取数据函数, request 就是axios实例
export function getCrypto1(params) {
  return request({
    url: "/echo",
    method: "post",
    params,
    encryption: false,
    hideMsg: true,
    notSign: true,
    notToken: true
  });
}

// App.vue created

function sleep(time) {
   return new Promise((resolve) => setTimeout(resolve, time));
}

for (let i = 0; i < 5; i++) {
  console.log("for: ", i);
  // await sleep(500);
  getCrypto1({ a: 11111 }).then((res) => {
    console.log("res--P: ", res);
  }).catch((err) => {
    console.log("res--E: ", err);
  });
}

#1 axios自带的取消请求(AbortController)

完整代码(request.js):

// 用于存储请求标识和对应的 AbortController
const pendingRequests = new Map();

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    cancelRequestHandler(config);
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    cancelRequestHandler(response.config, "repsonse");
  },
  (error) => {
    if (error.config) {
      cancelRequestHandler(error.config, "repsonse");
    }
    if (error.name === "CanceledError") {
      return;
    }
    return Promise.reject(error.response);
  }
);

// 生成唯一请求标识的方法
function generateRequestKey(config) {
  const { method, url, params, data } = config;
  return `${method}:${url}:${JSON.stringify(params)}:${JSON.stringify(data)}`;
}

// axios AbortController取消请求
function cancelRequestHandler(config, requestType = "request") {
  // console.log("--cancel:  ", config, requestType);
  // 创建请求标识
  const requestKey = generateRequestKey(config);

  if (requestType === "request") {
    // 如果已存在相同请求,取消之前的请求
    if (pendingRequests.has(requestKey)) {
      const controller = pendingRequests.get(requestKey);
      controller.abort();
      pendingRequests.delete(requestKey);
    }

    // 为当前请求创建新的 AbortController
    const controller = new AbortController();
    config.signal = controller.signal;

    // 存储当前请求的控制器
    pendingRequests.set(requestKey, controller);
  } else {
    pendingRequests.delete(requestKey);
  }
}

请添加图片描述

#2 不发请求(axios)

大概思路就是 将重复请求在请求拦截器里用 return Promise.reject 直接进入响应拦截器错误里,在将结果返回即可

完整代码(request.js):

export const request = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

// 用于存储请求标识
const pendingRequests = new Map();

// 请求拦截器配置
request.interceptors.request.use(
  (config) => {
    const { shouldIntercept, promise } = handleDuplicateRequest(config);
    if (shouldIntercept) {
      return promise; // 如果是重复请求,返回挂起的 Promise
    }
    // 其它逻辑...
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  (response) => {
    resolvePendingQueue(response);
    // 其它逻辑...
    return Promise.resolve(decrypts);
  },
 (error) => {
    console.log("resp error: ", error);
    if (error.cachedResponse) {
      if (error.cachedResponse.status === 200) {
        return Promise.resolve(error.cachedResponse?.data);
      } else {
        return Promise.reject(error.cachedResponse?.data);
      }
    }
    if (error.config) {
      rejectPendingQueue(error.response);
    }
    // 其它逻辑...
    return Promise.reject(error.response);
  }
);

// 生成唯一请求标识的方法
function generateRequestKey(config) {
  const { method, url, params, data } = config;
  return `${method}:${url}:${JSON.stringify(params)}:${JSON.stringify(data)}`;
}

// 重复请求缓存
function handleDuplicateRequest(config) {
  const requestKey = generateRequestKey(config);
  if (pendingRequests.has(requestKey)) {
    return {
      shouldIntercept: true,
      promise: new Promise((resolve, reject) => {
        pendingRequests.get(requestKey).push({ resolve, reject });
      })
    };
  }
  // 如果是首次请求,初始化队列
  pendingRequests.set(requestKey, []);
  return { shouldIntercept: false, promise: null };
}

// 处理成功响应队列
function resolvePendingQueue(response) {
  const requestKey = generateRequestKey(response.config);
  const pendingQueue = pendingRequests.get(requestKey) || [];
  pendingQueue.forEach(({ reject }) => reject({ cachedResponse: response }));
  pendingRequests.delete(requestKey);
}

// 处理失败响应队列
function rejectPendingQueue(error) {
  const requestKey = generateRequestKey(error.config);
  const pendingQueue = pendingRequests.get(requestKey) || [];
  pendingQueue.forEach(({ reject }) => reject({ cachedResponse: error }));
  pendingRequests.delete(requestKey);
}

示例((成功):

请添加图片描述

示例(失败):
请添加图片描述

以上只发了一次请求

#3 不发请求(推荐,适合所有请求库)

基本思路: 对于第一个请求, 将promise存起来, 在该请求响应之前, 所有的相同请求都公用一个promise, 当第一个请求响应时,所有的promise都释放了,所有阻塞的相同请求都能拿到相同的结果!

完整代码(request.js)

export const request = axios.create({
  baseURL: 'https://some-domain.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

// 创建缓存请求
export function createCacheedRequest(config) {
  // 根据请求的 URL 和参数生成唯一的请求 key
  const requestKey = generateRequestKey(config);
  // 判断请求是否已经在进行中
  if (!pendingRequests.has(requestKey)) {
    // 如果没有该请求,发起请求并缓存该请求的 Promise
    const requestPromise = service(config).finally(() => {
      // 请求完成后,从 pendingRequests 中删除该请求
      pendingRequests.delete(requestKey);
    })

    // 缓存该请求的 Promise
    pendingRequests.set(requestKey, requestPromise);
  }

  // 返回正在进行的请求 Promise
  return pendingRequests.get(requestKey);
}

// 不用再拦截器添加代码

// 调用的方式需要改一下
// 获取数据
export function getCrypto1(params) {
  return createCacheedRequest({
    url: "/echo",
    method: "post",
    params,
    encryption: false,
    hideMsg: true,
    notSign: true,
    notToken: true
  });
}

请添加图片描述

当打开睡眠时间时

for (let i = 0; i < 5; i++) {
  await sleep(500); // 每过500ms发出一个请求
  console.log("for: ", i);
  getCrypto1({ a: 1111 }).then((res) => {
    console.log("res--P: ", res);
  }).catch((err) => {
    console.log("res--E: ", err);
  });
  // this.getCryptoData();
}

// node服务
// 接口耗时1500ms
app.post('/echo', (req, res) => {
  console.log('received : ', req.body)
  const data = req.body;
  setTimeout(() => {
    res.json({ code: 0, msg: 'success', data: 22222 });
    // res.status(500).json({ code: -1, msg: 'error', data: null });
  }, 1500)
});

这样就会第1个请求结束后,和第2,3,4 一起返回, 第五个请求重新发,不会被取消,单独返回。总共发出了两次请求

请添加图片描述


http://www.kler.cn/a/415470.html

相关文章:

  • Spring Boot开发——整合JPA配置多数据源
  • Java多线程学习二
  • YOLOv9改进,YOLOv9引入CAS-ViT(卷积加自注意力视觉变压器)中AdditiveBlock模块,二次创新RepNCSPELAN4结构
  • canal同步数据教程
  • HTML DOM 修改 HTML 内容
  • Facebook广告无法投放是什么原因?
  • NLP信息抽取大总结:三大任务(带Prompt模板)
  • Java面向对象高级学习二
  • Spring MVC 深度剖析:优势与劣势全面解读
  • k8s 1.28 二进制安装与部署
  • CPU、MPU、MCU和SOC学习笔记
  • 今日codeforces刷题(1)
  • 【Canvas与雷达】简约绿色扫描雷达图标
  • 【Linux】Linux常用命令
  • .net6.0 mvc 传递 model 实体参数(无法对 null 引用执行运行时绑定)
  • Python 3 教程第36篇(XML 解析)
  • 小程序 - 婚礼邀请函
  • 微信小程序Webview与H5通信
  • Vision Transformer(vit)的主干
  • CSS学习记录02
  • AI开发:逻辑回归 - 实战演练- 垃圾邮件的识别(一)
  • SpringMVC跨域问题解决方案
  • BUUCTF—Reverse—GXYCTF2019-luck_guy(9)
  • 003 MATLAB基础计算
  • Cesium 当前位置矩阵的获取
  • 深入探索 Java 中的 Spring 框架