LoginSignup
3
1

More than 3 years have passed since last update.

[lodash] [async.js] jsのいろいろなforEachを試してみた

Last updated at Posted at 2020-10-26

はじめに

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

lodash documentation

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 documentation

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
 */

まとめ

まとめてみると、意外とそれぞれで違いがあることがわかりました。
それでは

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1