土炮處理:api非同步時間差造成的response順序問題

問題——B頁面顯示A資料:

在 SPA 下,User 點了A頁面,送出 A request。但是頁面出來前,立馬切換到B頁面,最後頁面B顯示A的資料。

思路:

  1. 原生取消 request => 2. 用 UI Loading 擋 User => 3. 幫 request 加流水號,去最新的 respond

1. 原生取消request

一開始想要做 request cancel,但我們因爲是使用 fetch 的關係,原生不支援 abort 功能,另外要支援 IE 11 還要加多一包 polyfill,覺得太麻煩就放棄了。

2. 用UI Loading擋User

我的同事一直主張用這個方式,而我後來拒絕的原因,是因爲這比較適用在 Form 提交的情境下,若 User 誤點還要等資料回來才能點, User 應該會很惱火XD

註:適用在 Form Submit

3. request加流水號

基本上沒什麼問題,唯一的風險,大概就是 User 的人數多起來,Server 的負擔有點重,這個後期再處理好了。

其實這個 idea 是從 RxJS 得到靈感的XD 一開始是想到 huli 大大在 Modern Web講 RxJS 提到的 Switch Map,但案子快完成了根本不可能。我就想能不能自己土炮做出類似Switch Map,於是就有了加流水號的想法。具體想法是在 call api 之前,在全域加上變數,再來到 callApi() 裏面,用 timestamp 加上 Uid,在 Api 回傳之後比對是不是最新的 Uid。看不懂直接看下面的code

1
2
3
4
5
6
7
8
9
10
11
12
const variable = {
lastestUid: ''
}

function callApi() {
const apiUid = new Date().getTime()
variable.lastestUid = apiUid
return Api.fetchUserData().then( () => {
if (apiUid !== variable.lastestUid) return
// update api data here
})
}

這樣其實就解決了問題,還有可以優化的地方,把 new Date() 換成從0開始的 counter,效能會更快:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const variable = {
--lastestUid: ''
++lastestUid: 0
}

function callApi() {
--const apiUid = new Date().getTime()
++const apiUid = variable.lastestUid + 1
variable.lastestUid = apiUid
return Api.fetchUserData().then( () => {
if (apiUid !== variable.lastestUid) return
// update api data here
})
}

後期處理

這樣的方法畢竟還是應急,之後會 User 亂點對 Server 負擔還是很大的。詢問群組的大大之後,覺得這樣的方法還不錯:

1
2
3
4
5
6
7
8
9
10
個人作法上會偏向用 UI Loading Modal 鎖使用者
因為要去 check 最後一次 update 這件事會比較容易出錯

Fetch Request 時,要把 Timeout 弄短一點,UI 擋 refresh,出現3秒之類的 loading,
Timeout 短,那使用者至少不會太生氣 (如果網路慢),然後用 loading 去鎖使用者亂點

然後晚回來的資料可以存在類似 redux 的資料中心,這樣下次還可以使用

沒及時回來:用 local 舊的資料
及時回來:用剛拿到的即時資料

但是這樣的方法好優化好多東西哦,除了 Fetch 沒有 Timeout 的功能,如果 Timeout 後晚回來的資料要怎麼拿來更新data而不被丟掉,目前也是個問題,加上可能要自幹改很多東西,可能下次再試試看好了XD

結論

這件事要做得漂亮,不加重 Server 的負擔或降低 UX 真的要花很多功夫,而且原生沒有支援很多,很多眉眉角角都要自己處理,下次是不是要開始使用RxJS了呢XD

參考資料

  1. https://stackoverflow.com/questions/46946380/fetch-api-request-timeout
  2. https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort
  3. https://drive.google.com/file/d/1HS5uBGfWf8900HJE3otnKQIex9c-KEd7/view