天天看点

window.location.href如何多次请求_如何解决异步请求的竞态问题

window.location.href如何多次请求_如何解决异步请求的竞态问题

如何解决异步请求的竞态问题

疫情期间 大家带好口罩 ^ . ^

免责声明

作者学艺不精又懒的要死,本文如有错误 概不负责 欢迎指出 随缘改正

部分图片可能挂了,强烈建议还是去 git 看吧,这破乎没法玩了

如何解决异步请求的竞态问题​github.com

引子

这大概是所有前端在实际工作中都要解决的问题吧。。。

在现在的交互场景中,搜索框里的实时下拉提示,地图缩放时的数据更新等等。。

只要你多次触发同一个动作 多次调用了同一个接口,你就要考虑时序的问题

这次所讨论的内容并不是给请求加

loading

的判断啊,或者节流啊防抖啊,或者

async/await

啊之类的

因为在下面的场景中这些并不能解决问题。。

请注意,我们的接口响应时间是随机的,而且我们要得到最新的结果,也就是最后一个请求得到的响应

场景描述

现在页面上有一个输入框,随着用户的输入会不断发出异步请求,取回后端返回的结果渲染在页面上。

但是蛋疼的是,接口的响应时间并不确定,也就是说,有可能先请求的后返回,后请求的却先返回了。

如果不作处理,这会导致前端渲染的结果错误(不是最后一个请求返回的结果)

那么。。如何才能保证页面正确的渲染呢?

重现

我们可以先简单重现一下,领会一下精神,代码如下,效果如图

window.location.href如何多次请求_如何解决异步请求的竞态问题

参见链接 https://github.com/YuArtian/blog/blob/master/assets/%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E7%AB%9E%E6%80%81%E9%97%AE%E9%A2%98/abort_0.html">abort_0.html

window.location.href如何多次请求_如何解决异步请求的竞态问题

以上

可以看到我们实际想渲染的是第四个请求的结果,但是却被第三的请求的结果后来居上了,导致了显示错误

简单粗暴的解决方式

最简单的想法就是记一下数(其实已经记了)。。。比较一下是不是最后一个就好了

window.location.href如何多次请求_如何解决异步请求的竞态问题

参见链接 https://github.com/YuArtian/blog/blob/master/assets/%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E7%AB%9E%E6%80%81%E9%97%AE%E9%A2%98/abort_1.html">abort_1.html

window.location.href如何多次请求_如何解决异步请求的竞态问题

OK,这样看起来已经阔以了

但是这样简单的方法在实际使用中并不方便,你总是需要想办法返回一个计数,然后做比较判断。。每个请求还都要重写一遍。。

鉴于现代的异步请求基本上都会使用

Promise

,接下来我们就介绍一种结合了

Promise

XHR.abort()

的船新方法

XHR.abort() 和 "终止"请求

XML

HttpR

eques

t 提供的 abort 方法 可以用来将

XMLHttpRequest

readyState

置为 0。这样就可以视为请求被 "终止" 了 ^ . ^

但是请注意,这只是前端视角上的"终止",实际上请求还是会到达服务器的(后面我们会证明这一点)。

从 http 原理来讲,也没可能会有所谓的终止请求的。简单设想一下,前端发出一个删除数据的请求,正常的请求流程中,请求被服务器接受,后端操作数据库,删掉数据,然后把结果响应给前端。在这中间前端如果真的能在响应到达之前终止请求的话,那删掉的数据怎么办呢。。。 -,-

所有的终止方法都只能是在到达前端之后不做处理而已,这样在用户看来就是被"终止"了。

真实场景搭建

为了能真实的测试我们的请求和响应的情况,我们就真的写一个随机响应的

node

服务出来吧 ~

新建一个

app.js

, 代码如下

window.location.href如何多次请求_如何解决异步请求的竞态问题

参见链接 https://github.com/YuArtian/blog/blob/master/assets/%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E7%AB%9E%E6%80%81%E9%97%AE%E9%A2%98/app.js" >app.js

同样的,我们的前端请求也要写一个真实的

现代的异步请求离不开

Promise

,即使你用

XHR

也建议用

Promise

包装

实际上,这样的封装只有一次,整个项目都会使用这个封装好的

xhrAdapter

window.location.href如何多次请求_如何解决异步请求的竞态问题

参见链接 https://github.com/YuArtian/blog/blob/master/assets/%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E7%AB%9E%E6%80%81%E9%97%AE%E9%A2%98/abort_2.html">abort_2.html

接下来,测试一下是否能重现我们要的场景

可以看到,请求按顺序发出了,但是响应是不定时的。现在舞台搭好了。

window.location.href如何多次请求_如何解决异步请求的竞态问题

Promise 和 XHR.abort()

之前,我们解决问题的思路是给请求计数,通过对比来判断出最后一个请求

现在,换一种思路。在连续的请求过程中,每当我发出一个请求,我就将之前正在

pending

的请求的

Promise

reject

掉,并且该请求的

XHR

对象执行

abort()

之前的请求 如果已经有响应的不用管它,我们当前的请求的结果会覆盖它的

这样就能确保最后的响应是正确的了

那么问题来了,怎样才能记录之前的请求,还要能在适当的时机执行对它的一系列操作呢?

这个问题,其实

Promise

自己就是答案。仔细想一想,只要

Promise

的状态改变了,就会在

.then

或者

.catch

中执行我们之前写好的回调函数,而且利用这个回调函数,刚好可以用来保存之前的请求。简直完美

这样的话,就需要我们自己生成一个

Promise

并把它的

.then

回调关联到我们的

xhrAdapter

中,回调函数中会保存当时的

XHR

请求对象和其包装

Promise

reject

方法,有了这两个对象就可以达到我们的目的了。

那下面我们就来具体实现一下这个想法

首先,总不能每次都写一遍生成

Promise

的代码。这里就构造一个名为

CancelToken

的类用来生成

Promise

,为了防止多次执行取消操作,也对取消请求操作进行记录, 需要同样的构造一个

Cancel

的类

这样的话其实也有一个小问题,就是这个两个类和具体发出请求的方法,也就是

handleInput

肯定是不在一起的了,那如何才能在外部(

handleInput

中)控制一个

Promise

的状态呢?

具体的答案我们还是先来看一下代码就知道了

window.location.href如何多次请求_如何解决异步请求的竞态问题

然后,还要对

xhrAdapter

进行改造。为了不具有侵入性,这里读取参数中的

cancelToken

配置,有这个参数的请求才进入控制

window.location.href如何多次请求_如何解决异步请求的竞态问题

参见链接 https://github.com/YuArtian/blog/blob/master/assets/%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E7%AB%9E%E6%80%81%E9%97%AE%E9%A2%98/abort_3.html">abort_3.html

实际使用的时候,方法如下

window.location.href如何多次请求_如何解决异步请求的竞态问题

实际效果:

window.location.href如何多次请求_如何解决异步请求的竞态问题

看来效果还是阔以的,而且写法高端了很多

实际上,上面的实现就是

Axios

的源码的简陋版本

Axios

相关文档 Cancellation部分

Axios

源码位置 Cancel部分,XHR部分

fetch 和 AbortController

然而并不是所有的请求都是使用

XHR

的,使用

fetch

的并不少见。用了

fetch

的,可以使用

AbortController

来阻止请求。

AbortController abort 来自MDN

abortable-fetch

下面是用

fetch

AbortController

结合使用的例子

window.location.href如何多次请求_如何解决异步请求的竞态问题

参见链接:https://github.com/YuArtian/blog/blob/master/assets/%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82%E7%9A%84%E7%AB%9E%E6%80%81%E9%97%AE%E9%A2%98/abort_4.html">abort_4.html

实际效果:

window.location.href如何多次请求_如何解决异步请求的竞态问题

尾声

至此,我们已经处理了两种前端常见的请求方式

XHR

fetch

的竞态问题。当然也只是这种一种场景下的竞态问题,前端会有更多复杂的异步问题需要面对。所以才会有

RxJS

等解决异步问题的库出现。

以后也许会去深入研究更为复杂的异步问题 ^ . ^