はじめに
javascriptでforEachを書くパターンは色々あります
使用していて「このループを書くならこっちのほうがいいな」「このforEachって非同期処理のときどうなるんだっけ」と思い返すことが多々あったので記事にまとめておこうと思いました。
今回取り上げるのは以下です
・ Array.prototype.forEach
・ lodash forEach
・ async.js each
・ async.js eachSeries
サンプルコード内で非同期関数 asyncFunc
, arr
を呼び出していますが、これはそれぞれ
const arr = [1, 2, 3];
// 一秒後に引数の関数を実行する関数
const asyncFunc = i => {
setTimeout(() => {
console.log(`output: ${i}`);
}, 1000);
}
このように定義しています。
Array.prototype.forEach
Array.prototype.forEach documentation
arr.forEach(element => {
asyncFunc(element)
});
/**
* 一秒後にまとめて結果が返ってくる
output: 1
output: 2
output: 3
*/
Array.prototype.forEach
はまさにノンブロッキングの形になっていて、forEach内の要素が順にイベントキューに積まれて行くといったイメージでしょうか
ちなみに、 Array.prototpye.forEach
はcontinueは可能(returnするだけ)ですが、breakは不可能です
また、要素を同期的に実装するのも厳しいです。(自分の知る限りでは)
lodash forEach
const _ = require('lodash');
_.forEach(arr, element => {
asyncFunc(element)
});
/**
* 一秒後にまとめて結果が返ってくる
output: 1
output: 2
output: 3
*/
こちらも Array.prototype.forEach
と同様に要素がすべてイベントキューに積まれていく形です。
ただちょっと違うのは、 breakができる という点です
const _ = require('lodash');
_.forEach(arr, element => {
console.log(`start: ${element}`)
asyncFunc(element)
if (element === 1) return false;
});
/**
* 1だけ出力してbreakされている
start: 1
output: 1
*/
このように、イベントキューに1,2,3それぞれの非同期処理が積まれていても、1の処理でbreakされれば後続の処理はされずに終了します。
また、 Array.prototype.forEach
と同様に同期的に実行するのも厳しいです
小ネタとしてはdocumentにも書かれていますがObjectに対しても実行可能です。
_.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
console.log(`${key}:${value}`);
});
/**
* objectに対しても利用可能
a:1
b:2
*/
async.js each
async.each(arr, (elem, next) => {
asyncFunc(elem)
next();
}, (err) => {
console.log(err);
});
/**
* 一秒後にまとめて結果が返ってくる
output: 1
output: 2
output: 3
*/
こちらも同様に要素がすべてイベントキューに積まれていく形です。
breakの機能はありませんが、callbackにエラーを投げることが出来ます。
// 実行要素の途中でエラーが発生した場合
async.each(arr, (elem, next) => {
console.log(`start: ${elem}`)
asyncFunc(elem)
if (elem >= 2) return next('err occured');
else return next();
}, (err) => {
if (err) console.log(err)
});
/**
* エラーが発生したタイミングでcallbackが呼び出されるが、asyncFuncは全て実行される
start: 1
start: 2
err occured
start: 3
1
2
3
*/
// 実行要素がすべてエラーを返さなかった場合
async.each(arr, (elem, next) => {
console.log(`start: ${elem}`)
asyncFunc(elem)
return next();
}, (err) => {
if (err) console.log(err)
else console.log('error not occured')
});
/**
* すべての処理をキューに詰め込んだあとでcallbackが呼び出される
start: 1
start: 2
start: 3
error not occured
1
2
3
*/
↑のように、イベントキューに積まれている非同期処理はその途中でエラーが発生(nextの第一引数にエラー内容を指定)しても関係なくすべて実行されます。また、エラーが発生した際はその直後にcallback関数(第三引数)が呼び出されます。
一度もエラーが発生しなかった際は、すべての処理をイベントキューに詰め込んだあとでcallbackが呼び出されます。
ちょっと特殊っぽい。
async.js eachSeries
async.eachSeries(arr, (elem, next) => {
asyncFunc(elem)
return next();
}, (err) => {
if (err) console.log(err)
});
/**
* 一秒後にまとめて結果が返ってくる
output: 1
output: 2
output: 3
*/
先程の async.js each
の同期的バージョンです。 each
では処理の順番は保証されていませんでしたが、 eachSeries
では実行順が保証されています。
またそのような性質から このように next
を活用することで同期的にすることも可能です。
const _asyncFunc = (i, cb) => {
setTimeout(() => {
console.log(i);
return cb();
}, 1000);
}
async.eachSeries(arr, (elem, next) => {
_asyncFunc(elem, next)
}, (err) => {
if (err) console.log(err)
});
/**
* 一秒ごとに出力される
output: 1
output: 2
output: 3
*/
まとめ
まとめてみると、意外とそれぞれで違いがあることがわかりました。
それでは