- 大量のデータを順番にサーバーに送りたい。
- 大量のデータをブラウザのデータベースに順番に保存したい。
等、配列に収められた一連のデータを、順番に (同期的に) 処理したいけど、非同期処理が含まれてしまうため、タイミング調整するのが難しい、というケースが有ります。
解決法のひとつとして、Promise を使った同期処理 という記事を以前書いたのですが、Array.reduce
を使った例が非常にエレガントだったので、分かりやすく?紹介します。
参考資料:Promise waterfall
Array.reduce()
は配列の中身を順番に関数に投げ、結果を累積的に処理していくためのものです。例えば
var result = [1, 2, 3, 4].reduce(function(prevResult, currValue, index, array) {
return prevResult + currValue;
}, 0);
console.log(result);
.reduce
の第一引数となる関数は 4 つの引数を取ります。
- 前回の結果
- 今回検証する配列の値
- 今回検証する配列の番号
- 配列そのもの
.reduce
の第二引数となる値は、最初の検証時の 前回の結果
を指定することができます。省略すると、インデックス 0 がこれに割り当てられ、実際の検証はインデックス 1 からスタートすることになります。上記の例では
順番 | 前回の結果 | 今回検証する配列の値 | 今回検証する配列の番号 | 配列そのもの |
---|---|---|---|---|
1回目の検証 | 0 | 1 | 0 | [1,2,3,4] |
2回目の検証 | 1 | 2 | 1 | [1,2,3,4] |
3回目の検証 | 3 | 3 | 2 | [1,2,3,4] |
4回目の検証 | 6 | 4 | 3 | [1,2,3,4] |
というわけで、結果は 10 ということになります。
これを応用して毎回 Promise
を返すことでチェーンさせる例が下記になります。
var wait = function(val) {
return new Promise(resolve => {
// 1秒待ちを入れてコンソールに数字を表示する
// XHR 等の非同期処理でも良い
setTimeout(() => {
console.log(val);
resolve();
}, 1000);
});
};
// 配列を順番に処理する
var sequential = function(array) {
// .reduce で順番に処理。初期値は第二引数の Promise.resolve()
return array.reduce((promise, val) => {
// promise が常に前回の戻り値であることを利用して
// Promise を連鎖させるのがポイント
return promise.then(res => wait(val));
}, Promise.resolve());
}
// 実行すると Console に 10 からのカウントダウンが 1 秒置きで表示される
sequential([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);