JS非同步控制流及async實現細節分析(1)

發表於2015-11-29

本系列文章使用的async版本為v1.5.0.

JS的非同步函式執行,大致上可以分為以下幾種:

  • 所有非同步任務並行執行
    • 無最大並行數限制
    • 有最大並行數限制
  • 所有非同步任務序列執行
  • 序列執行與並行執行相結合

1. 並行執行(無最大並行數限制)

這是最簡單的一種非同步執行流程。只需要一個簡單的arr.forEach就可以完成。

考慮如下的一種情形:給定一系列的檔名,分別讀取檔案內容並輸出,當讀取完所有的檔案後,提示任務完成。

為了得知是否所有任務都已經執行完,我們引入一個初始值為0的計數器,每當一個任務完成的時候,就給計數器加1,然後檢測計數器的值是否等於輸入的檔案個數,如果相等,則說明所有任務執行完畢。

簡單的程式碼實現如下:

但是每次遇到這種情況,都需要手動維護計數器比較麻煩,因此需要將這個過程封裝為一個函式,從而方便呼叫。我們期望的函式呼叫是如下形式:

注意: eachOf的第三個引數callbackfn的第三個引數callback是不同的函式。

fncallback是由使用者手動呼叫的,而eachOfcallback是在所有任務完成後由eachOf函式內部自動呼叫的。

即對於arr中的每一項,呼叫fn,當所有任務執行完後,呼叫callbackeachOf的初步實現如下:

在這裡也看以看出,fncallback其實是內部提供的done函式。但是這樣還存在著一個潛在的問題,如果在fn中多次呼叫了callback,那麼當一個任務完成後,就會多次呼叫done,此時計數器就無法正確計數。因此,需要確保每個任務完成後,無論使用者呼叫多少次callbackdone都只能執行一次。

為此,實現一個once函式,如下:

然後將fn(val, i, done);替換為fn(val, i, once(done));即可。

在async中,提供瞭如下方法:

它們的第一個引數不僅可以是一個陣列,還可以是類陣列或者物件。它們的區別只是在於iterator的函式簽名不同。

2. 並行執行(有最大並行數限制)

相當於有一個池子(類比於執行緒池),當池子不飽和的時候,就向裡面加入任務。當所有任務都加入到了池子後,等待所有任務完成,然後執行最後的回撥函式。如果在執行過程中出錯,那麼不再繼續執行後面的任務。

因此,需要三個變數:

  • i表示當前任務的序號
  • running表示當前正在執行的任務的個數
  • errored表示是否出錯

簡單實現如下:

例子如下:

在async中,提供瞭如下方法:

3. 序列執行

序列執行也就是說只有當一個任務完成後,才會繼續執行下一個任務。比較典型的就是Express中介軟體的執行。

這種情況下通常依賴於一個next函式,該函式用來取出一個任務並執行,當任務完成後,遞迴呼叫next從而繼續下一個任務的執行。

簡單實現如下:

呼叫如下:

但是這樣的實現還有一個小問題,當fn內部都是同步操作時,例如:

此時輸出為:

為了解決這個問題,引入一個sync變數,用來表示當前處於同步執行還是非同步執行。修改後的程式碼如下:

done中對sync進行判斷,如果synctrue,則說明fn是一個同步操作,此時需要setImmediate(next),將下一次呼叫變為非同步操作。

在async中,提供瞭如下方法:

相關文章