如何消除http请求,http常见的几种请求方式

首页 > 经验 > 作者:YD1662022-11-09 14:27:52

如何消除http请求,http常见的几种请求方式(1)

场景:

作为开发者,我们接触最多的就是CRUD,各种接口的联调,但是好像会比较少的去关注我们发送的http请求,当这个请求没有fulfilled,而又有新的请求发送出去,那么当前这一个请求应当如何处理?我们知道为了防止重复的动作,我们能使用类似于防抖和节流来规避,但是今天我们谈谈从请求层面上如何去规避重复的请求,而不是从用户侧去阻止。其实网上也有很多这方面的开发者的尝试,但是有的也讲得没有那么清楚。为此,在调研了这方面的一些知识之后,结合自己平时在开发中遇到的场景。总结出来两个最常见的http请求需要被取消的场景。

场景一:

相同的请求需要取消,这里的相同的请求指的是对于get 请求来讲,method一样,params一样,url一样,对于Post请求来讲,当然就是method一样,body一样,url一样

场景二:

路由地址发生变化(之前网页的请求已经无意义)

实现方法:

首先,要想实现重复的取消,我们就需要做两步,第一步是首先要知道如何取消,第二步就是如何判断出当前是重复的请求

取消重复请求实现方法:

关于如何取消,这部分我会从axios以及fetch两个方面来阐述,因为这两个请求方式的实现取消的方法是有差别的

为了方便理解,我们通过react来演示这个过程更直接,因为我们知道钩子函数useEffect,useEffect的特点决定了返回函数会在每一次useEffect执行之前,进行一次,来进行清理操作,因此在这里,我们把取消操作放在这儿,就可以模拟每一次都取消前一次的操作

axios

首先介绍axios,这个大家前端er们肯定接触得都要吐了,一句话概括,axios实现取消的本质就是使用其内部封装的cancelToken。

首先要知道,token确定了唯一性,这也是确定哪一个请求需要被取消的标识,它可以由cancelToken.source()生成

source包含了cancel方法 ,我们调用它来实现取消

useEffect(() => { const cancelToken = axios.CancelToken; const source = cancelToken.source(); setAxiosRes("axios request created"); getReq(source).then((res) => { setAxiosRes(res); }); return () => { source.cancel("axios request cancelled"); }; }, [axiosClick]);

export const instance = axios.create({ baseURL: "http://localhost:4001", }); export const getReq = async (source) => { try { const { data } = await instance.get("/", { cancelToken: source.token, }); return data; } catch (err) { if (axios.isCancel(err)) { return "axios request cancelled"; } return err; } };

这里需要注意的是,cancel这个动作,本身就是可以被catch部分给捕捉到的,也是一个err,我们用它提供的isCancel方法去判断一下是否是取消操作,这个可以用来验证我们的取消是否成功

fetch:

那么fetch的情况就不一样了,fetch的实现方式则是通过signal来取消,其内部的AbortController()能取消掉所有响应signal标记的请求,同样用react来模拟一次,其实本质还是一样的,并且同样也能被catch到

export const instance = axios.create({ useEffect(() => { const controller = new AbortController(); const signal = controller.signal; setFetchRes("fetch request created"); hitApi(signal).then((res) => { setFetchRes(res); }); //cleanup function return () => { controller.abort(); }; }, [fetchClick]);

hitApi函数如下,也就是把signal放进我们的fetch里面,这样我们才能abort。

export const hitApi = async (signal) => { try { const response = await fetch("http://localhost:4001/", { signal }); const data = await response.json(); return data; } catch (err) { if (err.name === "AbortError") { return "Request Aborted "; } return err; } }

这里同样的,'AbortError'可以在catch被捕获到

判断是否重复

好了,取消的功能实现了,接下来就要考虑如何去判断请求是否重复了。那毋庸置疑,判断是否重复,想要常数级时间复杂度来找到是否有重复的请求,当然是使用Map,这样可以以O(1)的速度找到那个重复的请求,从而才能决定去取消。而且,可以想到,整个过程需要向数组里面加东西,而已经被取消过的当然就需要拿出来,因此我们需要一个加的函数,以及一个移除的函数

const addPending = (config) => { const url = [ config.method, config.url, qs.stringify(config.params), qs.stringify(config.data) ].join('&') config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => { if (!pending.has(url)) { // If the current request does not exist in pending, add it pending.set(url, cancel) } }) }

当给 config.cancelToken赋值的时候应当注意,当前这个 config.cancelToken是否已经有值了

为了方便我们平铺参数,我们可以用qs来转换Object到string

const removePending = (config) => { const url = [ config.method, config.url, qs.stringify(config.params), qs.stringify(config.data) ].join('&') if (pending.has(url)) { // If the current request identity exists in pending, you need to cancel the current request and remove it const cancel = pending.get(url) cancel(url) pending.delete(url) } }axios拦截器

然而在实际项目中,我们通常有axios拦截器来统一管理我们的请求,所以这里也有很多人喜欢直接把这两个方法加进axios拦截器里,这样就一劳永逸了。

axios.interceptors.request.use(config => { removePending(options) // Check previous requests to cancel before the request starts addPending(options) // Add current request to pending // other code before request return config }, error => { return Promise.reject(error) })

axios.interceptors.response.use(response => { removePending(response) // Remove this request at the end of the request return response }, error => { if (axios.isCancel(error)) { console.log('repeated request: ' error.message) } else { // handle error code } return Promise.reject(error) })路由切换使用方式

最后再来说一说第二个场景,路由切换了的情况,比较简单,直接清空我们的pending队列就好,直接怼:

export const clearPending = () => { for (const [url, cancel] of pending) { cancel(url) } pending.clear() }

router.beforeEach((to, from, next) => { clearPending() // ... next() })效果演示:

如何消除http请求,http常见的几种请求方式(2)

可以看到,重复点击,请求是cancelled,而如果是成功返回,则是200。

原理分析:

function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; //把内部暴露出来 }); var token = this; //executor(cancel方法); executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested return; } //token.reason是Cancel的实例 token.reason = new Cancel(message); resolvePromise(token.reason);//改变promise的状态 }); }

CancelToken的核心本质上来讲其实是将promise挂载上去,然后自己不去主动resolve或者reject,而是把这个主动权先暴露出来,也就是代码里的resolvePromise,然后在取消的函数中去改变这个promise的状态。

改变这个状态有什么用呢,需要结合xhrAdapter源码来理解,在这里我们就可以看到是在什么地方abort的了,标红部分就是通过上文改变promise状态,.then里面被执行的过程。

function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { if (config.cancelToken) { //请求中,监听cancelToken中promise状态改变 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); request = null; }); } }) } 结语:

其实http请求取消不是什么很稀奇的事情,要实现类似的请求取消,有很多种其他的方法,甚至很多方法是优于这一种实现方法的,比如说取消当前这个请求代替取消前一个,似乎更合乎逻辑一些,但是此文着重的是这一种暴露resolve的思想,非常值得一鉴。

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.