Promiseでループ処理

javascriptで通信処理を記述するときに使用するPromiseオブジェクト。
だいぶ慣れてきたが、ループ処理についてはよくわからず立ち止まってしまったので解決を考えてみたいと思う。

満たしたい要件

  1. ループ内の処理はPromiseを返却する
  2. ループ回数を指定できる
  3. ループ内処理に、異なるパラメータを渡せる(1)
  4. ループ内でエラーが起きた場合、catchできる

単純にループを

まず1,2を満たすコードを書いてみる。

// ループ内処理
// @param {Number} i - ループカウンター
function action(i) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      console.log(i + '回目です')
      res();
    },500)
  })
}

// ループ関数
// @param {Function} fn - 個々の処理
// @param {Number} i - カウント初期値
// @param {Number} end - カウント終了値
function loop( fn, i, end) {

  return fn(i)
  .then( () => {
    if ( i < end ) {
      return loop( fn, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })

}

// 実行
loop(action, 0, 3 );

// console
< 1回目です
< 2回目です
< 3回目です
< 4回目です
< end

ループ内処理では、setTimeoutを使用して擬似的に遅延させてPromiseを返却している。
Promiseでのループ関数のポイントは再帰処理だと思ってる。ループ内処理を呼び出して、thenでPromiseをチェーンさせる2。then内で引数の値からloopを終了させるかどうか判定し、続けるならばloopを再帰的に呼び出し、終了ならばPromiseを返すようにした。

エラー処理の追加

続いて、ループない処理でエラーが生じた場合それ以降の処理を中断するように改良する。

function action(i) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      // 修正
      if ( i === 5 ) {
        rej('5は処理できません');
      }
      else {
        console.log(i + '回目です')
        res();
      }
    },500)
  })
}

function loop( fn, i, end) {

  return fn(i)
  .then( () => {
    if ( i < end ) {
      return loop( fn, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })
  // 追加
  .catch( (err) => {
    console.log(err);
  })

}

// 実行
loop( action, 0, 6);
// console
< 0回目です
< 1回目です
< 2回目です
< 3回目です
< 4回目です
< 5回は処理できません

ループ内処理では、5だとrejectするように擬似的に設定。 ループ関数では、最後にcatchを追加するだけでよい。

任意のパラメータ指定

最後に3.の指定した任意のパラメータを実行させるように改良してみる

// ループ内処理
// 引数を追加
function action(i, second) {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      if ( i === 5 ) {
        rej('5回は処理できません');
      }
      else {
      console.log(i + '回目です');
      // 追加
      console.log('パラメータ合計:' +  Number(i + second) ); 
      res();
      }
    },500)
  })
}

// ループ関数
// params引数を追加
function loop( fn, params, i, end) {

  // applyを使って、個々のparamを渡す
  return fn.apply(null, params[i])
  .then( () => {
    if ( i < end ) {
      return loop( fn, params, i+1, end)
    }
    else {
      return Promise.resolve('end');
    }
  })
  .catch( (err) => {
    console.log(err);
  })

}

// 個々のパラメータを用意
var params = [
  [1,10],
  [2,20],
  [3,30],
  [4,40]
]

// 実行
loop(action, params, 0, 3 )
// console
< 1回目です
< パラメータ合計:11
< 2回目です
< パラメータ合計:22
< 3回目です
< パラメータ合計:33
< 4回目です
< パラメータ合計:44
< end

修正はループ関数だけでよい。
むしろ、ループを回すために内部処理に微修正させるとかしたくない。 fnを直接呼び出すのではなく、applyメソッドでparamsをfnの引数として 利用するように指定する。applyめっちゃ便利。


  1. ループ内処理は同じ処理である

  2. チェーンさせて処理をつなげるため、whileやfor文ではループ処理ができないのだと思っている。