中断已发出去的请求

项目中遇到过频繁切换标签的问题,由于不同标签请求的ajax的结果所需时间不同,以及网络环境等因素,我们点击不同标签时,响应时间最慢的数据会覆盖之前响应的数据,显示数据跟所点击标签不对应,即竞态问题

 1. 交互层面解决

在发起请求后,我们添加全局的 loading 遮罩,或者 disabled 查询按钮 ,这样我们在一个请求未完成前不能发送新的请求。

2.取消已发送的请求

2.1 AbortController

AbortController 接口表示一个控制器对象,可以根据需要终止一个或多个Web请求。

  • AbortController(): AbortController()构造函数创建一个新的 AbortController 对象实例

  • signal:signal 属性返回一个 AbortSignal 对象实例,它可以用来 with/about 一个Web(网络)请求

  • abort():终止一个尚未完成的Web(网络)请求,它能够终止 fetch 请求,任何响应Body的消费者和

let controller;
const url = "video.mp4";

const downloadBtn = document.querySelector(".download");
const abortBtn = document.querySelector(".abort");

downloadBtn.addEventListener("click", fetchVideo);

abortBtn.addEventListener("click", () => {
  if (controller) {
    controller.abort();
    console.log("中止下载");
  }
});

function fetchVideo() {
  controller = new AbortController();
  const signal = controller.signal;
  fetch(url, { signal })
    .then((response) => {
      console.log("下载完成", response);
    })
    .catch((err) => {
      console.error(`下载错误:${err.message}`);
    });
}
  • 兼容性

 

2.2 axios 中断请求

2.2.1 方式一

使用 CancelToken.souce 工厂方法创建一个 cancel token,代码如下:


// CancelToken是一个构造函数,用于创建一个cancelToken实例对象
// cancelToken实例对象包含了一个promise属性,值为可以触发取消请求的一个promise
const CancelToken = axios.CancelToken;

// 执行source()得到的是一个包含了cancelToken对象和一个取消函数cancel()的对象
// 即 source = {token: cancelToken对象, cancel: 取消函数}
const source = CancelToken.source();

// 在请求的配置中配置cancelToken,那么这个请求就有了可以取消请求的功能
axios.get('https://mdn.github.io/dom-examples/abort-api/sintel.mp4', {
  cancelToken: source.token
}).catch(function (thrown) {
  // 判断请求是否已中止
  if (axios.isCancel(thrown)) {
    // 参数 thrown 是自定义的信息
    console.log('Request canceled', thrown.message);
  } else {
    // 处理错误
  }
});

// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');

 2.2.2 方式二

通过传递一个 executor 函数到 CancelToken 的构造函数来创建一个 cancel token:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    // 把cancel函数传递给外面,使得外面能控制执行取消请求
    cancel = c;
  })
});

// 取消请求
cancel('Operation canceled by the user.');

2.2.3 封装使用

import axios from 'axios'
import qs from 'qs'

// 用于存储pending的请求(处理多条相同请求)
const pendingRequest = new Map()

// 生成request的唯一key
const generateRequestKey = (config = {}) => {
  // 通过url,method,params,data生成唯一key,用于判断是否重复请求
  // params为get请求参数,data为post请求参数
  const { url, method, params, data } = config
  return [url, method, qs.stringify(params), qs.stringify(data)].join('&')
}

// 将重复请求添加到pendingRequest中
const addPendingRequest = (config) => {
  const key = generateRequestKey(config)
  if (!pendingRequest.has(key)) {
    config.cancelToken = new axios.CancelToken(cancel => {
      pendingRequest.set(key, cancel)
    })
  }
}

// 取消重复请求
const removePendingRequest = (config) => {
  const key = generateRequestKey(config)
  if (pendingRequest.has(key)) {
    const cancelToken = pendingRequest.get(key)
    cancelToken(key) // 取消之前发送的请求
    pendingRequest.delete(key)// 请求对象中删除requestKey
  }
}

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 处理重复请求
    removePendingRequest(config)
    addPendingRequest(config)

    return config
  },
  error => {
    // 处理请求错误
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 移除重复请求
    removePendingRequest(response.config)
    
    return res
  },
  error => {
    // 异常情况console,方便排查问题
    console.log('error', error)
    // 移除重复请求
    removePendingRequest(error.config || {})
    
    return Promise.reject(error)
  }
)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值