シアトルコンサルティング サービス開発ブログ

シアトルコンサルティング株式会社プロダクトソリューション事業部の開発ブログです

Async.jsで繰り返し処理

satkakuです。Async.jsの繰り返し処理で少しはまったので、それについてのメモです。

Async.jsは非同期処理のフロー制御をしてくれるライブラリで、順番に処理を書けるseriesや、前回の処理結果を次の処理に渡せるwaterfallなどがあります(以前の記事)。

そんなAsync.jsには、配列を非同期で処理するために便利なforEachがあります。

# IDが配列に入っているとする
idList = [ "1", "2", "3" ]

# IDに紐づくデータを非同期で取得する
async.forEach(idList, (val, next)=>

  repository.findByID(val, (err, res)=>
    # 取得結果を別の配列に入れる
    @result.push(res)
    next()
  )

, ->
  # 配列の全ての結果が返ってきた際に呼ばれる
  done()
)

こんな感じでやると、簡単に配列内のデータについて全て非同期で処理できます。

……しかし実際にこの処理を動かすと、resultという配列に入る結果は[ "1", "2", "3" ]の順序が保たれていません。何故? となったところで気付きました。forEachに引数として渡す関数の引数名を「next」としており、さも配列が順番に処理されるっぽく書いていますが、これは自分で勝手につけた名前です。ここは「callback」という名前にすべきでしょう。あくまで、各非同期処理の終了を告げるためのもののようです。

結論から言うと、forEachは順番を保証しないので、forEachSeriesを使います。

# IDが配列に入っているとする
idList = [ "1", "2", "3" ]

# IDに紐づくデータを非同期で取得する
async.forEachSeries(idList, (val, callback)=>

  repository.findByID(val, (err, res)=>
    # 取得結果を別の配列に入れる
    @result.push(res)
    callback()
  )

, ->
  # 配列の全ての結果が返ってきた際に呼ばれる
  done()
)

これで、resultの中身はidListの順番と同じになります。

・順番を保証したい場合はforEachSeriesを使う

今回の教訓は、実際の動きを分かっていないうちに、思い込みで変数名をつけると痛い目を見るということです。分かんなかったらソース読めという話ですね。まだソース読んでないので、改めてちゃんと読んでおこうと思いました。