背景简介
我们日常开发中,经常会遇到点击一个**「按钮」** 或者进行**「搜索」**时,请求接口的需求。
如果我们不做优化,连续点击**「按钮」** 或者进行**「搜索」**,接口会重复请求。 ❝
首先,这会导致性能浪费!最重要的,如果接口响应比较慢,此时,我们在做其他操作会有一系列bug! ❞
那么,我们该如何规避这种问题呢?
如何避免接口重复请求
防抖节流方式(不推荐)
使用防抖节流方式避免重复操作是前端的老传统了,不多介绍了
防抖实现
<template> <div> <button @click="debouncedFetchData">请求</button> </div> </template>
<script setup> import { ref } from 'vue'; import axios from 'axios';
const timeoutId = ref(null);
function debounce(fn, delay) { return function(...args) { if (timeoutId.value) clearTimeout(timeoutId.value); timeoutId.value = setTimeout(() => { fn(...args); }, delay); }; }
function fetchData() { axios.get('http://api/gcshi) // 使用示例API .then(response => { console.log(response.data); }) }
const debouncedFetchData = debounce(fetchData, 300); </script>
「防抖(Debounce)」 :
-
在setup函数中,定义了timeoutId用于存储定时器ID。
-
debounce函数创建了一个闭包,清除之前的定时器并设置新的定时器,只有在延迟时间内没有新调用时才执行fetchData。
-
debouncedFetchData是防抖后的函数,在按钮点击时调用。
节流实现
<template> <div> <button @click="throttledFetchData">请求</button> </div> </template>
<script setup> import { ref } from 'vue'; import axios from 'axios';
const lastCall = ref(0);
function throttle(fn, delay) { return function(...args) { const now = new Date().getTime(); if (now - lastCall.value < delay) return; lastCall.value = now; fn(...args); }; }
function fetchData() { axios.get('http://api/gcshi') // .then(response => { console.log(response.data); }) }
const throttledFetchData = throttle(fetchData, 1000); </script>
「节流(Throttle)」 :
-
在setup函数中,定义了lastCall用于存储上次调用的时间戳。
-
throttle函数创建了一个闭包,检查当前时间与上次调用时间的差值,只有大于设定的延迟时间时才执行fetchData。
-
throttledFetchData是节流后的函数,在按钮点击时调用。
节流防抖这种方式感觉用在这里不是很丝滑,代码成本也比较高,因此,很不推荐!
请求锁定(加laoding状态)
请求锁定非常好理解,设置一个laoding状态,如果第一个接口处于laoding中,那么,我们不执行任何逻辑!
<template> <div> <button @click="fetchData">请求</button> </div> </template>
<script setup> import { ref } from 'vue'; import axios from 'axios';
const laoding = ref(false);
function fetchData() { // 接口请求中,直接返回,避免重复请求 if(laoding.value) return laoding.value = true axios.get('http://api/gcshi') // .then(response => { laoding.value = fasle }) }
const throttledFetchData = throttle(fetchData, 1000); </script>
这种方式简单粗暴,十分好用!
「但是也有弊端,比如我搜索A后,接口请求中;但我此时突然想搜B,就不会生效了,因为请求A还没响应」 !
因此,请求锁定这种方式无法取消原先的请求,只能等待一个请求执行完才能继续请求。
axios.CancelToken取消重复请求
基本用法
axios其实内置了一个取消重复请求的方法:axios.CancelToken
,我们可以利用axios.CancelToken来取消重复的请求,爆好用!
首先,我们要知道,aixos有一个config
的配置项,取消请求就是在这里面配置的。
<template> <div> <button @click="fetchData">请求</button> </div> </template>
<script setup> import { ref } from 'vue'; import axios from 'axios';
let cancelTokenSource = null;
function fetchData() { if (cancelTokenSource) { cancelTokenSource.cancel('取消上次请求'); cancelTokenSource = null; } cancelTokenSource = axios.CancelToken.source(); axios.get('http://api/gcshi',{cancelToken: cancelTokenSource.token}) // .then(response => { laoding.value = fasle }) }
</script>
我们测试下,如下图:可以看到,重复的请求会直接被终止掉!
CancelToken官网示例
❝
官网使用方法传送门:https://www.axios-http.cn/docs/cancellation ❞
const CancelToken = axios.CancelToken; const source = CancelToken.source();
axios.get('/user/12345', { cancelToken: source.token }).catch(function (thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // 处理错误 } });
axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token })
// 取消请求(message 参数是可选的) source.cancel('Operation canceled by the user.');
也可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建一个 cancel token:
const CancelToken = axios.CancelToken; let cancel;
axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // executor 函数接收一个 cancel 函数作为参数 cancel = c; }) });
// 取消请求 cancel();
注意: 可以使用同一个 cancel token 或 signal 取消多个请求。
在过渡期间,您可以使用这两种取消 API,即使是针对同一个请求:
const controller = new AbortController();
const CancelToken = axios.CancelToken; const source = CancelToken.source();
axios.get('/user/12345', { cancelToken: source.token, signal: controller.signal }).catch(function (thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // 处理错误 } });
axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token })
// 取消请求 (message 参数是可选的) source.cancel('Operation canceled by the user.'); // 或 controller.abort(); // 不支持 message 参数