【Javascript】Promiseの使い方 4パターン

Promiseとは

Promiseは SPA(シングルページアプリケーション)を構築するうえではもはや欠かせない、非同期処理を制御するためのオブジェクトです。

  • ある非同期処理が完了するのを待って、次の処理を動作させる
  • 「ある非同期処理が完了するのを待って、次の処理を動作させる」を複数並行で動作させる
  • 複数の非同期処理を並行で動作させ、すべて完了するのを待って、次の処理を動作させる
  • 複数の非同期処理を直列で動作させる
スポンサーリンク

ある非同期処理が完了するのを待って、次の処理を動作させる

サンプルの非同期処理として、下記のような関数 asyncMethod() を準備しました。
これは引数で指定されたミリ秒だけ待機したのち、文字列 id + 'finished' を解決値とするという動作をします。
この関数は、実際のシステムではサーバー上のリソースを取得するなどの処理に該当します。

function asyncMethod(id, waitTimeMillis) {
  return new Promise(function(resolve) {
    setTimeout(function(){
      resolve(id + ' finished');
    }, waitTimeMillis);
  });
}

この関数を呼び出すための関数 main1() を準備します。

function main1() {
  console.info('main start');

  asyncMethod('A', 3000).then(
    function(result){
      console.info(result);
    }
  );

  console.info('main end');
}

main1()を呼び出してみると、開発者ツールのコンソールに下記のように表示されるはずです。

main start
main end
A finished

解説

main1() は、まずは console.info('main start') を処理します。
次に asyncMethod() を呼び出します。ここまでは特別なことは何もありません。
ただ、その後、その処理が完了するのを待たず、次のステップを処理します。これが非同期処理です。
ここで言う次のステップとは、then() の中ではなく、console.info('main end') です。
そして 3000ms 後にasyncMethod が解決されて、console.info(id + ' finished') が処理されます。

「ある非同期処理が完了するのを待って、次の処理を動作させる」を複数並行で動作させる

パラメータが配列に格納されているケースを考えてみます。
Array.prototype.forEach() を使って、ループ処理します。

functin main2() {
  console.info('main start');
  
  var promiseArray = [];

  var params = [
    ['A', 2000],
    ['B', 3000],
    ['C', 1000]
  ];

  params.forEach(function(param, index){
    asyncMethod(param[0], param[1]).then(function(){
      console.info(result);
    });
  });

  console.info('main end');
}

main2()を呼び出してみると、下記のように表示されます。

main start
main end
C finished
A finished
B finished

よくハマる間違った実装

ここで単純に for ループのループカウンタ i を使って、下記のように実装してしまうと、意図した結果が得られないので注意が必要です。

functin main3() {
  console.info('main start');
  
  var params = [
    ['A', 2000],
    ['B', 3000],
    ['C', 1000]
  ];


  for (var i = 0; i < params.length; i++) {
    var param = params[i];
    asyncMethod(param[0], param[1]).then(function(result){
      // then の外側の変数を参照すると、意図しない値が入ることがある
      console.info(param[0] + ' finished.');
    });
  });

  console.info('main end');
}

main3() が回しているループカウンタと、asyncMethod()が参照しているループカウンタは、非同期処理のため一致するとは限りません。その結果、下記のように表示されてしまいます。

main start
main end
C finished
C finished
C finished

複数の非同期処理を並行で動作させ、すべて完了するのを待って、次の処理を動作させる

Promise.all を使って、下記のように実装します。

function main4() {
  console.info('main start');
  
  var promiseArray = [];

  var params = [
    ['A', 2000],
    ['B', 3000],
    ['C', 1000]
  ];

  params.forEach(function(param, index){
    promiseArray.push(
      asyncMethod(param[0], param[1]).then(function(result){
        console.info(result);
      }));
  });

  Promise.all(promiseArray).then(
    function(resultArray){
      console.info('all done');
    }
  );

  console.info('main end');
}
main start
main end
C finished
A finished
B finished
all done

もっとシンプルに

これは、Array.prototype.map() を使って、下記のように短く書くことができます。

function main5() {
  console.info('main start');
  
  var params = [
    ['A', 2000],
    ['B', 3000],
    ['C', 1000]
  ];

  Promise.all(
    params.map(function(param){
      return asyncMethod(param[0], param[1]).then(function(result){
        console.info(result);
      });
    })
  ).then(
    function(resultArray){
      console.info('all done');
    }
  );

  console.info('main end');
}

複数の非同期処理を直列で動作させる

「1つ目が終わるのを待って2つ目を起動し、2つ目が終わるのを待って3つ目を起動する」といったことを繰り返す方法です。

function main6() {
  console.info('main start');
  
  var promise = Promise.resolve();

  var params = [
    ['A', 2000],
    ['B', 3000],
    ['C', 1000]
  ];

  params.forEach(function(param, index){
    promise = promise.then(function(){
      return asyncMethod(param[0], param[1]).then(function(result){
        console.info(result);
      });
    });
  });
  
  promise.then(function(){
    console.info('all done');
  });

  console.info('main end');
}

これを実行すると、下記のように表示されます。

main start
main end
A finished
B finished
C finished
all done
スポンサーリンク
おすすめの記事