Analysis of axios canceling repeated requests

This article has participated in the "Newcomer Creation Ceremony" activity, and started the road of Nuggets creation together

foreword

Regarding canceling repeated requests, the most important thing is the meaning of doing so, not the implementation of the code

In fact, I think that most of the application scenarios that can be imagined can be realized through anti-shake and throttling methods, such as real-time search, such as repeated order submission, such as pulling up to obtain the latest data, etc. We know that the so-called cancellation request is in The request has been issued for execution, but if the cancellation is timely, the entire request process has not been completed, and the request may only be sent to the server, but it has not received the return result from the server. The result is only processed

Of course, the benefits of doing this are obvious. For example, when pulling up and loading data, requests may be issued frequently during the entire pull-up process. After each request returns, the data must be rendered on the page. Frequent operations will cause the page jitter, etc. If the repeated request is canceled, the request will only be issued, but the result returned by the previous server will not be processed, and only the last returned result will be processed, so that this problem can be avoided.

Let’s talk briefly below, and I hope that some students can systematically cancel repeated requests. From the perspective of front-end and back-end coordination, I will talk about it systematically.

1. The request always arrives at the server

This section is just to prove one point: cancel the request, and the request will also reach the server!

It makes sense to think about how to prevent problems with duplicate commits

1.1 XMLHtppRequest

Let's illustrate this first using the most basic XMLHtppRequest object

1. The server code accepts a get request and returns data

const express = require('express')
const cors = require('cors')

const app = express()
app.use(cors())


app.get('/users', (req, res, next) => {
   const obj={
       name:'yhb',
       age:20
   }
   console.log('请求已到达')
    res.send(obj)
})
复制代码

2. Client code

After the button is clicked, send the ajax request and then cancel the repeat request

 document.querySelector('button').addEventListener('click', () => {
    const xhr = new XMLHttpRequest()
    const url = 'http://127.0.0.1:3000/users'
    xhr.open('get', url)
    xhr.onreadystatechange = () => {
        console.log(xhr.readyState)
    }
    xhr.send()
    // 使用 about 方法取消重复请求
    setTimeout(() => {
        xhr.abort()
    }, 1000)
})
复制代码

3. Test

Under normal network conditions, it is meaningless to cancel the request, because the network speed is fast enough, and the server does not set a delay, so your button click speed is not as fast as the network.

We can change the browser's network to "low speed 3G", and then click the button repeatedly to find that the request has been cancelled

image.png

look at the server

image.png

It can be seen that although the request has been cancelled, the request still reaches the server

Therefore, from this point of view, it is absolutely unfeasible for businesses such as submitting orders to avoid generating multiple orders by canceling repeated requests. Prevent the generation of duplicate orders

4 Conclusion

那么,取消重复请求,也就是 about 方法,到底做了什么事情呢?

下面是 MDN 上关于 about 方法的说明

image.png

可见,about 方法不会,也不能阻止请求发出,只是发出后就不管了,不再接受服务端返回的数据

我们通过在不同的网络情况下,看一下输出的结果

image.png

正常网络

image.png

低速3G

输出的只有 4

通过对照 readyState 属性值的含义,我们明白

image.png

如果 readyState 的值,如果只输出一个 4,就证明,整个网络请求,没有经过 获取服务端返回的头部信息和状态信息下载服务端返回的数据,这两个阶段,而是直接认为请求已经完成,所以我们在网络中观察不到任何东西

image.png

1.2 axios 取消重复请求

axios 是现在非常流行的一个用于网络请求的库,如果应用在网页端,其内部调用的是 XMLHttpRequest 对象(我也没去验证最新版是不是这样),所以取消请求,其实内部调用的应该还是 about 方法

下面是代码,具体代码含义,后面会详细介绍,这里只要明白,执行结果与上面一模一样就好了

document.querySelector('button').addEventListener('click', () => {
    const CancelToken = axios.CancelToken;
    let cancel
    axios.get('http://127.0.0.1:3000/users', {
        cancelToken: new CancelToken(function executor(c) {
            cancel = c
        })
    }).then(res => {
        console.log(res.data)
    }).catch(err => {
        console.log(err)
    })

    setTimeout(() => {
        cancel('Operation canceled by the user.'); // 取消请求,参数是可选的
    }, 1000)
})
复制代码

1.3 结论

通过上面的代码测试,验证了我们的结论

  • 所谓取消请求,只是等请求已经发出,且已经到达服务端之后的操作,并不能阻止请求发送
  • 无论是否取消请求,服务端都已经接收到请求信息,当然也包括随请求一起发送的参数信息
  • 客户端取消请求,只是取消了接受服务端返回信息的步骤

这个结论一定正确吗?谁知道呢!

大家自己提出意见吧!

2. 取消重复请求

本节结合实时搜索的场景,说明如何通过取消重复请求来避免一些问题

首先要明白,在网络状况良好,或者后端响应很快的情况下,取消重复请求是无效的,因为当你想要取消上次请求的时候,整个网络请求早已经完成

所以,我们下面的案例,是在“低速3G” 这种网络状况不好的情况下运行的,模拟的就是网络延迟、服务端响应慢这种情况下,如何通过取消重复请求,达到页面不抖动的目的

2.1 服务端代码

根据用户的输入返回搜索建议

const express = require('express')
const cors = require('cors')

const app = express()
app.use(cors())

app.get('/search', (req, res, next) => {
    const data = [
        { key: '春', result: ['春天', '春光明媚'] },
        { key: '春天', result: ['春风送暖', '春光无限'] },
        { key: '春天的', result: ['春天的风', '春天的风景'] },
        { key: '春天的风', result: ['春天的风是醉人的', '春天的风是芳香的'] }
    ]
    const key = req.query.key

    // 筛选
    const filter_data = data.filter(item => {
        return item.key === key
    })
    if (filter_data.length === 0) {
        return res.send([])
    }
    console.log(filter_data[0].key)
    res.send(filter_data[0].result || [])

})


app.listen(3000, () => {
    console.log('server is runnig at http://127.0.0.1:3000')
})
复制代码

2.2 客户端代码

<template>
 <div id="app">
   <div>
     <input type="text" v-model="key" @keyup="getSuggest" />
   </div>
   <div class="result">
     <div v-for="(item, index) in suggestList" :key="index">
       {{ item }}
     </div>
   </div>
 </div>
</template>

<script>
import axios from "./api/axios";
// import axios from "axios";
export default {
 name: "App",
 data() {
   return {
     key: "",
     suggestList: [],
   };
 },
 methods: {
   getSuggest() {
     const res = axios.get(`http://127.0.0.1:3000/search`, {
       params: {
         key: this.key,
       },
     });

     res
       .then((response) => {
         console.log(response.data);
         this.suggestList = response.data;
       })
       .catch((err) => {
         console.log("11");
       });
   },
 },
};
</script>

<style>
</style>



复制代码

2.3 测试

正常网络下:

  • 随着文本框的 key 事件发生,不断的有请求发送,且全部都请求成功
  • 文本框下方的搜索建议的显示是没有问题的

GIF 2022-4-8 15-57-31.gif

低速3G

  • 随着文本框的 key 事件发生,不断的有请求发送,且全部都请求成功
  • 文本框下方的搜索建议的显示发生了奇怪的现象:在输入过程中,搜索建议列表没有任何显示,但是当搜索结束一段时间后,搜索建议列表如同魔法般的在不断变化
  • 因为由于网络关系,请求的响应并不及时,不能及时更新搜索建议列表,但是过段时间后,很多来自服务端的响应几乎同一时刻返回,频繁的更新搜索建议列表,导致搜索建议的显示发生了抖动
  • 因为网络较慢,请耐心观察动图

GIF 2022-4-8 16-01-07.gif

总结:

  • 两种情况下都会随着文本框的输入,发送大量请求
  • 低速3G网络下,请求的响应并不及时,而是很有可能很多请求的响应非常非常密集的返回

下面我们看看如何在 axios 中取消重复的网络请求,一级取消重复请求后的变化

2.4 axios 取消重复请求

2.4.1 如何判断重复网络请求

如何判断重复请求呢?

请求方式、请求 URL 地址和请求参数都一样时,我们就可以认为请求是一样的

可以在每次发起请求时

  1. 根据当前请求的请求方式、请求URL地址、请求参数生成一个唯一的 key
  2. 再为每个请求创建一个专属的CancelToken(取消token)
  3. 将 key 与 cancel 函数以键值对的形式存储起来
  4. 当出现重复请求时,使用 cancel 函数取消前面的请求
  5. 将取消的请求从集合中移除

下面是具体的步骤

2.4.2 创建几个函数

下面三个函数,分别用于

  • 根据请求信息生成一个key
  • 将生成的key 和对应的 Cancel 函数生成对象存储到 map 中
  • 从 map 中删除某个网络请求对象

1、定义函数,根据请求生成key

// 根据请求信息,生成 key
const generateKey = (config) => {
  let {
    method,
    url,
    params,
    data
  } = config;
  return [method, url].join("&");
}
复制代码

上面提到:重复请求的判断标准是:请求地址、请求方式、请求参数一致

所以,上面代码中,我们从 config 对象中结构出 请求地址、请求方式、请求头和请求体中的参数

但是在生成 key 时,两个参数并没有参与,原因在于在实时搜索的场景下,每次的参数一定是不同的,如果将参数也参与到 key 的生成,就构不成重复请求,所以,我们只结合请求方式和请求地址生成 key

做人要灵活

2、添加请求对象到 map 中

/**
* @description 添加请求信息 **/
let pendingRequest = new Map();

function addPendingRequest(config) {  
  // 1、调用 generateKey 方法生成 key
  const requestKey = generateKey(config);
  /**
   * 2、为本次请求添加 cancelToken(取消令牌),
   * 同时将key 和 取消函数构成的对象添加到 map 中
   */
  config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
    if (!pendingRequest.has(requestKey)) {
      pendingRequest.set(requestKey, cancel);
    }
  });
}
复制代码

3、从map 中删除请求对象

/**
* @description 取消重复请求 **/
function removePendingRequest(config) {
  let requestKey = generateKey(config);
  if (pendingRequest.has(requestKey)) {
    // 如果是重复的请求,则执行对应的cancel函数
    let cancel = pendingRequest.get(requestKey);
    // 这行代码取消请求
    cancel(requestKey);
    // 将前一次重复的请求移除
    pendingRequest.delete(requestKey);
  }
}
复制代码

2.4.3 在拦截器中应用

任何请求发出时,首先在请求拦截器中执行 removePendingRequest 函数,此函数判断此次请求是否重复的请求,如果是,就取消请求,并从 map 中将上次i请求信息删除

然后调用 addPendingRequest 方法,为此次请求的配置中加上取消请求函数,并将请求添加到 map 中

任何响应返回时,无论成功还是失败,说明此时请求已经顺利完成,本次请求已经结束,都会调用 removePendingRequest 函数将本次请求信息从 map 中移除,否则下次发送请求时还会认为已经发送了请求,进而去删除它,其实它早是早已经完成的请求,没有任何意义

1、请求拦截器中调用函数

/**
* @description 请求拦截器 **/
axios.interceptors.request.use(
  function (config) {
    // 检查是否存在重复请求,若存在则取消已发的请求,则取消
    removePendingRequest(config);
    // 把当前请求信息添加到pendingRequest对象中,
    addPendingRequest(config);
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);
复制代码

2、相应拦截器中删除重复请求

/**
* @description 响应拦截器 **/
axios.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    removePendingRequest(response.config);
    return response;
  },
  function (error) {
    // 从pendingRequest对象中移除请求
    removePendingRequest(error.config || {});
    if (axios.isCancel(error)) {
      console.log("被取消的重复请求:" + error.message);
    }
    return Promise.reject(error);
  }

);
复制代码

上面并没有对 axios 中相关代码做详细讲解,因为这都属于套路的东西,自己看文档就能够理解,再加上调试,观察的就会更仔细

2.4.4 测试

正常网络下运行代码,效果与设置重复请求是一样,因为当你想取消上次请求时,上次请求早已经完成了,根本就取消不了的

但是在 低速3G 网络下,在频繁的请求中,前面的请求被取消,只有最后一次请求是成功的

GIF 2022-4-8 15-44-20.gif

再看一下控制台的输出,结合上面客户端的代码,可以得出结论

通过取消重复请求的方式,虽然不能控制网络请求的次数,但是取消之后,请求就没有响应了,就不会执行上面客户端代码中的 then 方法了,也就不会重新为变量suggestList赋值,自然也就不会执行搜索建议列表的渲染了,自然就没有抖动了

只有最后一次没有被取消的请求,响应到达后执行了 then 函数中的代码,其他都是执行的 catch 中的代码,因为取消请求后就会抛出异常,被我们 catch 到了

image.png

3. 总结

① 取消重复请求是有用的,但只在一定的条件下发挥作用,比如网络卡顿,或者服务端反应很慢等情况

② 请求一旦被取消,就不会执行 then 方法,而是执行 catch 方法中代码,而我们更新页面等代码都写到 then 中,自然也就不会导致页面抖动

③ 上面提到的网络卡顿等情况毕竟不是天天发生,正常情况下,取消重复请求无法达到第二点说的情况,因为请求的响应速度快于你取消的速度,所以即使加上了取消重复请求代码,但是当文本框内容发生变化时,仍然会有非常频繁的网络请求,进而有非常频繁的dom更新,所以这里一定要配合防抖或者节流才能够减少请求和dom更新次数

④ 请求即使取消,也是会到达服务端的

Guess you like

Origin juejin.im/post/7084161924123852830