連續同源非同步操作佇列

否子戈發表於2019-02-27

問題的發現

來自同源的多個非同步操作可能引起非同步衝突問題,特別是在網路請求時。同源操作產生了兩個ajax請求,它們的請求結果將用於渲染同一個區域,然而由於網路問題,先發出的請求後返回,導致最終得到的介面是錯誤的。

解決這個問題的最好辦法,是利用原生XHR的abort方法,在後一次操作時,將前一次操作引起的ajax請求給cancel掉。

但是在現實條件下,非同步操作並非都有cancel操作。js原生的Promise沒有,原生的fetch基於Promise也沒有。基於Promise的很多工具都沒有cancel操作。這種情況下怎麼解決這個問題呢?

其實方法是有的,就是直接丟棄Promise的推送,不執行它的resolve回撥即可。這樣,雖然非同步操作已經執行了,但不會對現有的環境造成任何副作用。(雖然這樣看上去浪費了非同步操作這個資源。)

問題的思考

如何來判斷是否要丟掉它的回撥?我們可以建立一個佇列,每次產生一個非同步操作時,就將它加入到佇列中,當佇列中存在操作物件時,每次只取最後一個,等待它推送結果,執行它的回撥,排在它前面的操作全部丟棄掉。

基於這樣的想法,我寫了一個工具。它為非同步操作建立佇列,並根據不同的場景實現不同的佇列操作形式。你可以通過這裡閱讀它的原始碼和文件。它提供了4種可供選擇的場景,開發者根據自己的實際場景選擇使用其中的一種。

問題的解決

基於上述的思考,我最終釋出了deferer-queue,你可以通過npm安裝這個包,在自己的專案中使用它。它的操作模式超級簡單,首先例項化一個queue物件,然後往這個queue push非同步操作,非同步操作被裝在一個函式中被push進佇列,它的回撥函式一定是按照push的順序執行。

import DefererQueue from `deferer-queue`

const queue = new DefererQueue()

const defer1 = () => new Promise((resolve, reject) => { ... })
const defer2 = () => axios.get(...)
const defer3 = async () => ...

queue.push(defer1).then(() => { console.log(1) })
queue.push(defer2).then(() => { console.log(2) })
queue.push(defer3).then(() => { console.log(3) })複製程式碼

無論defer1-3中的誰,執行後誰先返回結果,控制檯輸出的結果永遠是1 2 3(defer都成功的前提下),因為deferer-queue設計的就是保證回撥是按push順序執行。

要使用不同模式,只需要在例項化的時候,傳入一個options物件,將mode設定為parallel/serial/switch/shift中的一個即可:

const queue = new DefererQueue({
  mode: `switch`,
})複製程式碼

其他的用法一樣。這樣,你的佇列就會只採用最後一個push進佇列的defer的結果,即使你的佇列正在進行,只要有新的defer被push進去,那之前的所有操作都會被丟掉,佇列執行結果只取最後一個操作的結果。

關於具體的API使用細則,你可以閱讀它的文件。其中,利用axios的cancel能力那個地方非常有借鑑意義。

關於四種執行模式的詳細解釋,由於圖片限制,請到我的部落格進行閱讀。

相關文章