天天看點

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

等解決異步問題的庫出現。

以後也許會去深入研究更為複雜的異步問題 ^ . ^