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

發表於2015-12-01

8. series/parallel/parallelLimit

async.eachOfSeries(arr, iterator, callback)是對arr中的每一項,呼叫iterator函式,最終呼叫callback。也就是說,所有的非同步任務都是同一種型別,只是傳入的引數不同。例如對於一個目錄下的所有檔案,統計每個檔案的size。

因此async.eachOfSeries無法處理多個不同型別的非同步任務的情況,例如多個非同步任務,一個是請求遠端資料,一個是判斷檔案是否存在,還有一些其它型別的任務。這種情況下需要一個新的函式series

series是基於async.eachOfSeries來實現,基本邏輯如下:


  • 每個task的回撥,記作taskCallback
  • async.eachOfSeries第二個引數中的回撥,記作iteratorCallback
  • async.eachOfSeries的第三個引數也是一個回撥(相關程式碼參看eachOfSeries的實現),記作eachOfSeriesCallback
  • series的第二個引數,記作seriesCallback

現在來分析series的執行流程:

  • 依次執行每個task,在這個過程中,會由使用者呼叫taskCallback([1])
  • taskCallback其實是在series的實現中定義([2])
  • 在執行taskCallback的時候,會先將task的結果儲存在結果集合中([3]),然後呼叫iteratorCallback([4])
  • iteratorCallback的作用是判斷任務是否執行完畢,如果沒有執行完畢,則繼續執行下一個任務;如果任務執行完畢,則執行eachOfSeriesCallback(該部分的邏輯在async.eachOfSeries中實現)
  • 當所有task執行完畢後,或者任務執行過程中出錯時,會執行eachOfSeriesCallback([5])
  • eachOfSeriesCallback中,執行seriesCallback([6]),至此,series的執行過程結束

parallel/parallelLimitseries的實現類似,只不過是將async.eachOfSeries換成了async.eachOf/async.eachOfLimit來實現。

9. waterfall

series模型的多個任務之間是沒有依賴關係的。但是如果一個任務的執行依賴於上一個任務的結果,series就無法處理了,此時需要使用waterfall,即每個任務的執行結果會傳遞給下一個任務。

一個簡單的實現思路是:將每一步任務的結果儲存在一個變數中,然後將該變數傳入下一個任務作為引數。基本邏輯如下:


當然,這只是一個非常簡陋的實現,僅僅用來描述waterfall的實現邏輯。在async的原始碼中,async.waterfall的實現如下:

首先來看async.iterator,它的作用是將一個任務列表轉換成一個迭代器結構,例如:


該函式原始碼不難看懂,在此不做贅述。

接下來看wrapIterator,其中有一個_restParam比較影響閱讀,我們把該函式改寫為如下形式:

總的來說,wrapIterator返回了一個可以遞迴呼叫的函式,從而可以依次執行任務。這段程式碼比較不容易理解,但是實現的非常巧妙。

接下來看ensureAsync的實現(已經改寫為去掉_restParam的形式從而方便閱讀):

這與async.eachOfSeries中的實現是基本一致的。那麼為什麼要確保任務的執行是非同步呢?這是因為:在一個任務中呼叫callback會去執行下一個任務,假如所有的任務都是同步呼叫,而且任務非常多,那麼就會導致棧溢位。

關於該情況下棧溢位問題,可以通過如下例子來模擬:

如果n非常大,例如simulateOverflow(100000),就會導致如下錯誤:

如果將fns[k - 1]()改成setImmediate(fns[k - 1]),就不會有問題了。

相關文章