Promiseのループをする際、処理順を保ちつつ実行する

javascript
スポンサーリンク

はじめに

この記事は次のような方向けの記事となります。

  • 配列のデータに対してPromiseでループしつつ処理順を保ちたい
  • 繰り返し処理でPromiseをすると処理順がバラバラになるため直列(同期的)で実行したい

同期的にPromiseを実行する方法

まず複数のPromiseを同期的に実行する方法になります。
連続するPromiseの処理を同期的に実行する場合は、1回目のPromiseの処理後(then等)にメソッドチェーンで2回目のPromiseを実行する必要があります。
具体的には次ようになります。

new Promise(function(resolve, reject) {
  // 1回目の処理
  resolve();
}).then(function(result) {
  return new Promise(function(resolve, reject) {
    // 2回目の処理
    resolve();
  })
}).then(function(result) {
  // ループ後の処理
})

ただこのように実装した場合、ループの回数が決まってしまうためあまり良くないです。
それを解消する方法を次から紹介します。
個人でそれっぽく作ったものなのでもっといい方法はあると思います。

実装方法

forで実装する場合

ダメなパターン

for (let i=0; i<10; i++) {
  new Promise(function(resolve, reject) {
  	const randomNumber = Math.floor(Math.random() * (10 - 0)) + 0
    const time = randomNumber * (i+1)
    setTimeout(function() {
      console.log('ループ'+(i+1)+'回目')
      resolve()
    }, time)
  })
} 

これはforで繰り返し処理をする際にランダムな数字を計算後その値を待機時間としたsetTimeoutを行っています。
待機後は何回目のループか表示して同期的に処理が出来ているか確認をしています。
実行した場合次のようなでたらめな順番でログが出力されました。

ループ4回目
ループ2回目
ループ1回目
ループ3回目
ループ9回目
ループ5回目
ループ7回目
ループ6回目
ループ10回目
ループ8回目

理由としてはforでPromiseを繰り返し実行しているだけなのでメソッドチェーンになっていません。
なので1回目の処理が終わる前に次の処理に移っていしまいます。

正しく動くパターン

function promiseLoop(currentCount, endCount) {
  return new Promise(function(resolve, reject) {
    // endCount(繰り返し回数)になった場合、ループを終わらせる
    if (currentCount >= endCount) {
      return resolve(false);
    }
    const randomNumber = Math.floor(Math.random() * (10 - 0)) + 0;
    const time = randomNumber * (currentCount+1);
    setTimeout(function() {
      console.log('ループ'+(currentCount+1)+'回目');
      return resolve(true);
    });
  }).then(function(result) {
    if (result) {
      // resultがtrueの場合ループ続行
      return promiseLoop(currentCount+1, endCount);
    }
    // resultがfalseの場合、ループ後の処理を実行
    console.log('ループ終わり')
  })
}
promiseLoop(0, 10)

このように実装し実行すると次のように結果が表示されます。

ループ1回目
ループ2回目
ループ3回目
ループ4回目
ループ5回目
ループ6回目
ループ7回目
ループ8回目
ループ9回目
ループ10回目
ループ終わり

ループを同期的に実行するため「promiseLoop」という関数を作成しました。
promiseLoop関数は引数として現在処理中の数値と繰り返しを行う回数の数値を指定しています。
この引数を使ってforと同じように繰り返し処理を行います。
処理の大雑把な説明は次のようになります。

  • promiseLoopはPromiseを返却する関数。
  • currentCountとendCountを比較しresolveにtrueまたはfalseを設定する。
    この際、trueの分岐に入るときはループ内で行う処理を実行してからresolveを呼び出す。
  • Promiseの処理後(then等)で返却値を確認しtrueの場合、再度promiseLoop関数を呼び出す。
    falseの場合、ループ後の処理を実行する。

このようにすることでresolveがtrueの間はPromiseをメソッドチェーンにして繰り返し処理を実行することができます。

スポンサーリンク

配列のループで実装する方法

ダメなパターン

const array = [0,1,2,3,4,5,6,7,8,9]

array.forEach(function(count) {
  new Promise(function(resolve, reject) {
    const randomNumber = Math.floor(Math.random() * (10 - 0)) + 0;
    const time = randomNumber * (count+1);
    setTimeout(function() {
      console.log('ループ'+(count+1)+'回目');
      resolve();
    }, time);
  })
})

これもforと同じパターンでPromiseを繰り返し実行しているだけなのでメソッドチェーンになっていません。
実行すると次のような結果になります。

ループ2回目
ループ4回目
ループ1回目
ループ3回目
ループ6回目
ループ7回目
ループ9回目
ループ5回目
ループ10回目
ループ8回目

正しく動くパターン

function promiseLoop(array, currentIndex) {
    return new Promise(function(resolve, reject) {
    // 現在のカウントが配列の要素数を超えたら処理を終了
    if (currentIndex >= array.length) {
      return resolve(false);
    }

    const num = array[currentIndex]
    const randomNumber = Math.floor(Math.random() * (10 - 0)) + 0;
    const time = num * (currentIndex+1);
    setTimeout(function() {
      console.log('ループ'+(num+1)+'回目');
      return resolve(true);
    });
  }).then(function(result) {
    if (result) {
      // resultがtrueの場合ループ続行
      return promiseLoop(array, currentIndex+1);
    }
    // resultがfalseの場合、ループ後の処理を実行
    console.log('ループ終わり')
  })
}
const array = [0,1,2,3,4,5,6,7,8,9]
promiseLoop(array, 0)

実行結果

ループ1回目
ループ2回目
ループ3回目
ループ4回目
ループ5回目
ループ6回目
ループ7回目
ループ8回目
ループ9回目
ループ10回目
ループ終わり

今回もループを同期的に実行するため「promiseLoop」という関数を作成しました。
引数は変わり処理対象の配列と現在処理中の数値を指定しています。
処理の大雑把な説明は次のようになります。

  • promiseLoopはPromiseを返却する関数。
  • 配列の要素数と処理中の数値を比較しresolveにtrueまたはfalseを設定する。
    この際、trueの分岐に入るときはループ内で行う処理を実行してからresolveを呼び出す。
  • Promiseの処理後(then等)で返却値を確認しtrueの場合、再度promiseLoop関数を呼び出す。
    falseの場合、ループ後の処理を実行する。

ほとんどforと同じ作りになっていますがこうすることで配列でもPromiseをループで処理することが可能になります。

コメント

タイトルとURLをコピーしました