JavaScript

JavaScript 複数の非同期処理(setTimeout)を実行して待機してから次の処理に移る方法

はじめに

複数の非同期処理を行った後に処理を行うために、いろいろな方法があると思いますが、かなり古典的なアプローチを試してみました。原理を知ったり、シンプルなやり方として学んでおくのもいいかもしれないなと思い、記事書きます。

非同期処理のところは、process A~D の部分に setTimeoutを使っていますが、ここに ajax 通信を書いても同じことができます。

ソースコード1

まず、こんなもので動かしました。

index.html
<!DOCTYPE html>
<html lang="ja"><head>
    <meta charset="utf-8">
    <script src="./index.js"></script>
<script>
document.addEventListener("DOMContentLoaded",function(eve){
    main();
},false);
</script>

</head><body>
</body></html>
index.js
'use strict';

const main = function () {
  main01();
};

const main01 = function () {

  const randomInt = function(min, max) {
    return Math.floor( Math.random() * (max + 1 - min) ) + min;
  };

  const sleep = function(waitMsec) {
    var startMsec = new Date();
    while (new Date() - startMsec < waitMsec);
  };

  let processCounter = 0;

  processCounter += 1;
  setTimeout(() => {
    console.log('Process A start');
    sleep(randomInt(1000, 3000));
    console.log('Process A end');
    processCounter -= 1;
  }, randomInt(1000, 3000));

  processCounter += 1;
  setTimeout(() => {
    console.log('Process B start');
    sleep(randomInt(1000, 3000));
    console.log('Process B end');
    processCounter -= 1;
  }, randomInt(1000, 3000));

  processCounter += 1;
  setTimeout(() => {
    console.log('Process C start');
    sleep(randomInt(1000, 3000));
    console.log('Process C end');
    processCounter -= 1;
  }, randomInt(1000, 3000));

  processCounter += 1;
  setTimeout(() => {
    console.log('Process D start');
    sleep(randomInt(1000, 3000));
    console.log('Process D end');
    processCounter -= 1;
  }, randomInt(1000, 3000));

  var processCheck = () => {
    setTimeout(() => {
      if (processCounter === 0) {
        main02();
      } else {
        setTimeout(processCheck, 1000);
      }
    }, 1000);
  };
  processCheck();

  console.log('Finish Main Thread');
};

const main02 = function () {
  console.log('Finish All Process');
};

実行結果

実行結果はこのようになります。乱数だからA/B/C/Dの順番はばらばらですがこういう感じになります。

Finish Main Thread
Process A start
Process A end
Process C start
Process C end
Process B start
Process B end
Process D start
Process D end
Finish All Process

メインスレッドと呼ぶのが正しいかどうかわかりませんが、main01のメインスレッド部分の処理が終わった後に、A~Dのプロセスのどれかが走り終わり、全て終われば、main02に処理が移っています。

ソースコード1の説明

randomIntは、ある数からある数までの乱数を出力していて、今回の場合は待機秒数を適当に1~3秒で設定するために使っています。

sleepは、グルグルと重い処理をしている風にしていて1秒から3秒の間で待機しています。

Process A の部分に着目してみます。Process B,C,Dもすべて同じ仕組みです。

  processCounter += 1;
  setTimeout(() => {
    console.log('Process A start');
    sleep(randomInt(1000, 3000));
    console.log('Process A end');
    processCounter -= 1;
  }, randomInt(1000, 3000));

プロセスカウンターを1追加します。
setTimeoutで、1~3秒後に処理スタート
console.logで、スタートを知らせて
sleepで、1~3秒、じっと重い処理をやってる風にして
console.logで、終了を知らせます。
処理が終わればプロセスカウンターを1減らしています。

このプロセスカウンターを1秒タイマーでチェックします。
プロセスカウンターが0になるまで、タイマーを続ける動作をしています。

  var processCheck = () => {
    setTimeout(() => {
      if (processCounter === 0) {
        main2();
      } else {
        setTimeout(processCheck, 1000);
      }
    }, 1000);
  };
  processCheck();

複数のデータを集めるためのajax通信などを、processA~Dの部分に使ったりするときに、処理が終わればカウンターを1減らす、というようなことをすれば、プロセスチェックの方でそのカウンターを監視することによって処理終了時点がわかります。

ソースコード2

もう少し良さそうなやり方として processCheck のタイマー処理がいらない書き方ができました。

index.js
'use strict';

const main = function () {
  main01();
};

const main01 = function () {

  const randomInt = function(min, max) {
    return Math.floor( Math.random() * (max + 1 - min) ) + min;
  };

  const sleep = function(waitMsec) {
    var startMsec = new Date();
    while (new Date() - startMsec < waitMsec);
  };

  let processCounter = 0;
  let startFlag = false;

  processCounter += 1;
  setTimeout(() => {
    console.log('Process A start');
    sleep(randomInt(1000, 3000));
    console.log('Process A end');
    processCounter -= 1;

    if (startFlag && (processCounter === 0)) {
      main02();
    }
  }, randomInt(1000, 3000));

  processCounter += 1;
  setTimeout(() => {
    console.log('Process B start');
    sleep(randomInt(1000, 3000));
    console.log('Process B end');
    processCounter -= 1;

    if (startFlag && (processCounter === 0)) {
      main02();
    }
  }, randomInt(1000, 3000));

  processCounter += 1;
  setTimeout(() => {
    console.log('Process C start');
    sleep(randomInt(1000, 3000));
    console.log('Process C end');
    processCounter -= 1;

    if (startFlag && (processCounter === 0)) {
      main02();
    }
  }, randomInt(1000, 3000));

  processCounter += 1;
  setTimeout(() => {
    console.log('Process D start');
    sleep(randomInt(1000, 3000));
    console.log('Process D end');
    processCounter -= 1;

    if (startFlag && (processCounter === 0)) {
      main02();
    }
  }, randomInt(1000, 3000));

  startFlag = true;
  console.log('Finish Main Thread');
};

const main02 = function () {
  console.log('Finish All Process');
};

実行結果は変わりません。

startFlagで処理開始を合図しておいて、ProcessA~Dの終了時にプロセスが全部終わったかどうかを確認して main02に移動する、という動作になります。

こんな書き方で、非同期処理を複数同時実行してそれを待機することができる、ということでした。ご参考にどうぞです。