JavaScriptでPromiseの配列を直列に実行する

今日は JavaScript の非同期処理に使われるPromise
を直列に実行する方法をご紹介したいと思います。
JavaScript のforEach
、map
などのイテレータ関数は返す値の順番は保障しますが、コールバックの実行は順番を保証しません。
バッチなどの処理で、Promise
をどうしても配列の順番で直列に実行する必要がある場合、forEach
などでそのまま書くと順番がちぐはぐになってハマります。
対処
唯一コールバックを順番に実行するイテレータ関数のreduce
を使い、Promise
のthen
chain を作ることで、処理が直列で実行されます。
テスト
Node の File System で 0-9 の配列を元に 10 個のファイルを出力。fs
は非同期で手っ取り早く実装できるので。Promise
を返すwrite
ファンクションをforEach()
、Promise.all() + map()
、Promise + reduce()
でそれぞれ実行する。
write function
fs のappendFile
(ファイルがあれば追記、なければ作成)をPromise
で単純にラップしたものです。
ちなみにこの処理ならappendFileSync
使えば一発ですが、あくまでも非同期処理の例ということですので。。
実行コマンド
$ node -e "require('./promise_array_test.js').EXPORTED_FUNCTION()"
実行は Node CLI でスクリプト直書きで行います。(-e オプションはスクリプトの実行)
普通の forEach()
普通のforEach
です。当然ですが、lambda はパラレルで実行されます。
実行結果
$ node -e "require('./promise_array_test.js').forEachTest()"just forEach ===========done0 is done!2 is done!1 is done!8 is done!6 is done!5 is done!7 is done!4 is done!9 is done!3 is done!
done
が先に呼ばれて、そのあとパラレルでランダムに実行されています。
Promise.all と map()
Promise.all
はPromise
の配列をとってパラレルに実行し、それぞれの結果を正しい順番の配列で返します。(5 行目でPromise
を返すwrite
function を呼んで配列を作ってPromise.all
に渡しています。)
実行結果
$ node -e "require('./promise_array_test.js').promiseAllTest()"promise all ============1 is done!0 is done!5 is done!7 is done!4 is done!2 is done!3 is done!8 is done!6 is done!9 is done!done
Promise.all
のthen
チェーンがあるので、処理がパラレルで全て実行され、done
がプリントされます。
Promise と reduce()
reduce()
は配列の中の要素を順番に取ります。
第一引数のacc
(accumulator)にはその前の lambda の結果(返り値)が入り、第二引数cur
には配列の要素が入ります。JS のイテレータらしく第三引数にはcur
のindex
が入ります。
reduce()
の第二引数は初期値(initial value)として、最初にacc
に入る値を渡すことができます。
reduce((acc, cur) \=> acc + cur, 10);
初期値を渡さずに[0,1,2,3,4,5].reduce()
を実行する場合、初回はacc
には 0 が入り、cur
には 1、index
は 1 になりますので、注意が必要です。
関数の説明はさておき、要は『acc
にPromise
を入れることによって lambda のthen
チェーンを作る』という実装になります。
初期値にPromise.resolve()
を渡すことによって、acc.then()
のように始めることができます。
(ちなみにPromise.resolve()
はnew Promise((res, rej) => res())
と同じことです。Promise.reject()
も同様。)
実行結果
$ node -e "require('./promise_array_test.js').promiseReduceTest()"promise reduce ===========0 is done!1 is done!2 is done!3 is done!4 is done!5 is done!6 is done!7 is done!8 is done!9 is done!done
美しく順番に実行されています。