昨天晚上小夥伴發了我一個文章,標題很嚇人,叫《如果你想靠前端技術還房貸,你不能連這個都不會》,剛好戳中了我這個潛在富一代買不起房的痛點。嚇得我趕緊點進來看了一下,吭哧吭哧大概花了三個小時才搞出來,做完又開始發愁了,房貸可能交得起了,這首付是國家發還是自己領呢。
好了不扯了回到正題
問題
知乎地址 zhuanlan.zhihu.com/p/25259283
某個應用模組由文字框 input,以及按鈕 A,按鈕 B 組成。點選按鈕 A,會向地址 urlA 發出一個 ajax 請求,並將返回的字串填充到 input 中(覆蓋 input 中原有的資料),點選按鈕 B,會向地址 urlB 發出一個 ajax 請求,並將返回的字串填充到 input 中(覆蓋 input 中原有的資料)。
當使用者依次點選按鈕 A、B 的時候,預期的效果是 input 依次被 urlA、urlB 返回的資料填充,但是由於到 urlA 的請求返回比較慢,導致 urlB 返回的資料被 urlA 返回的資料覆蓋了,與使用者預期的順序不一致。
請問如何設計程式碼,解決這個問題?
建議可以自己先嚐試一下看能不能寫出來
想看答案的可以直接戳這裡
問題分析
首先這道題肯定是一個將非同步佇列按同步的順序返回的問題。先返回的如果不是佇列的第一個任務,那麼結果將會被保留下來,一直等到佇列第一個任務返回時出棧,輪到自己到第一個佇列時才執行。
開始
首先我們先寫html頁面
html很簡單,第一個按鈕點選後等3s返回結果。第二個按鈕點選1s後返回結果。為了讓效果更明顯,我們將返回的結果依次輸出在
container
這個class
中
然後我們寫js程式碼
我們首先模擬兩個請求
這樣就完成了按鈕繫結,將返回的結果輸出到頁面中這個方法,但是由於非同步返回時間的不同,他們的順序並不是按照點選的順序來的,這裡就到了問題的難點了。如何讓非同步的請求按照同步的結果返回?問題大概可以歸納為幾個步驟
- 首先要有一個執行佇列,佇列中的內容即為儲存的請求
- 當儲存的請求執行完畢時,給它設定一個執行完畢的狀態。同時觸發一個執行完畢的方法
- 在執行完畢的方法中,檢查佇列第一個索引中的內容是否執行完畢,如果執行完畢則執行後續程式碼並移出佇列。判斷第二個是否也執行完畢,以此類推。
程式碼如下
這裡可以看到,我們用了一個createSyncTask
方法,用高階函式的方法,將非同步的佇列傳入,並返回一個組裝後的高階函式。當呼叫這個高階函式時,就向taskQueue
裡push
進一個任務。同時讓它的Promise
一直處於pending
狀態,當執行完畢後,才改為resolve
。同時觸發佇列中已經有任務完成的方法triggerTaskFinish
而triggerTaskFinish
方法就很簡單了。判斷當前佇列第一個是否已經resolve
,如果resolve
。那麼就將它的Promise
方法狀態設定為resolve
,同時將返回值傳入。
那麼接下來,我們只需要將原來的非同步請求包裝一下即可實現需求
好了,那麼我們就可以實現任務是根據我們點選順序來實現的效果了。而且這種寫法,如果不需要同步了或者增加新的按鈕,後面改起來也比較方便。
完整js程式碼如下
總結
這道題感覺最難的地方就是怎麼讓非同步的請求能按請求的先後順序返回,當時為了考慮程式碼的維護性和複用性,最後用了一個單例物件(不知道算不算單例)和高階函式將常見的非同步請求封裝成帶同步佇列的非同步請求。當然結果還有不足之處,比如我們沒有做異常處理,如果有一個請求跪了怎麼辦,這些就交給你們解決啦。
如果有什麼不足的地方歡迎評論指正。畢竟我也只是個小前端