JavaScriptで非同期処理を実装するPromiseオブジェクトにおいて、複数の非同期処理を順番に処理する方法(async await)

JavaScriptで複数の非同期処理を想定した順番に処理させたい(直列処理)

JavaScriptで複数の非同期処理を想定した順番に処理させたい(直列処理)場合があります。

処理が終わった後に、次の処理をつなげる必要がある処理に使われます。

例えば、外部のデータを取得して、そのデータを使って処理を行う必要がある場合は外部データの取得を先にする必要があります。

そういった場合に、この書き方は役立ちます。

処理方法は2つあります。

  • Promiseのみで実装する方法
  • await・acyncを使って実装する方法

この記事ではPromiseとawait・asyncを組み合わせて実装する方法について紹介していきます。

オブジェクト

オブジェクト意味返り値
new Promise(関数)Promiseを作成するPromiseインスタンス
resolve(引数)Promiseインスタンス内で利用され、非同期処理の終了(成功)を宣言するメソッド。Promiseインスタンス.then(関数)の関数に対して引数の値を渡すことができる。なし
reject(引数)Promiseインスタンス内で利用され、非同期処理の終了(失敗)を宣言するメソッド。Promiseインスタンス.catch(関数)の関数に対して引数の値を渡すことができる。なし
Promiseインスタンス.then(関数)成功した時のコールバック関数を呼び出し実行Promise
Promiseインスタンス.catch(関数)失敗した時のコールバック関数を呼び出し実行Promise
Promise.all(配列)複数のPromiseを並列に実行するPromise
Promiseオブジェクトの仕様

サンプルコード

まずは処理全体をまとめる関数を定義した上で、それに対してasyncをつけます。これにより、その関数は非同期処理で実行されます。

あとはその中でPromiseインスタンスを複数展開して、その全てにawaitをつけます。これにより、それぞれのPromiseインスタンスは、その中の非同期処理が全て完了しない限り、次のPromiseインスタンスの実行ができなくなります。

それを踏まえたサンプルコードを紹介します。想定した順番で3秒ごとに処理を1つ行っています。

Output
処理履歴
Pug
table.table
			tbody
				tr
					th 処理履歴
					td#targetText
JavaScript
const targetText = document.getElementById("targetText");

//Promise関連の処理を1まとめにしておく
async function promiseFunctions() {
  await new Promise((resolve) => {
    setTimeout(() => {
      let additionalElement = document.createElement("div");
			additionalElement.textContent = `1つ目のPromise ${new Date().toLocaleTimeString()}`;
			targetText.appendChild(additionalElement);
      resolve();
    }, 3000);
  });

  await new Promise((resolve) => {
    setTimeout(() => {
      let additionalElement = document.createElement("div");
			additionalElement.textContent = `2つ目のPromise ${new Date().toLocaleTimeString()}`;
			targetText.appendChild(additionalElement);
      resolve();
    }, 3000);
  });
	await new Promise((resolve) => {
    setTimeout(() => {
      let additionalElement = document.createElement("div");
			additionalElement.textContent = `3つ目のPromise ${new Date().toLocaleTimeString()}`;
			targetText.appendChild(additionalElement);
      resolve();
    }, 3000);
  });
	await new Promise((resolve) => {
    setTimeout(() => {
      let additionalElement = document.createElement("div");
			additionalElement.textContent = `4つ目のPromise ${new Date().toLocaleTimeString()}`;
			targetText.appendChild(additionalElement);
      resolve();
    }, 3000);
  });
	await new Promise((resolve) => {
    setTimeout(() => {
      let additionalElement = document.createElement("div");
			additionalElement.textContent = `5つ目のPromise ${new Date().toLocaleTimeString()}`;
			targetText.appendChild(additionalElement);
      resolve();
    }, 3000);
  });
}

//ひとまとめにしてあるPromiseを実行
promiseFunctions();

このコードは予め実行する内容がわかっている物であれば十分ですが、もしデータを受け取り、そのそれぞれに対して同じような処理を加える場合は以下のコードの様にループ処理を組み合わせた方が便利な場合もあります。

Output
処理履歴
Pug
table.table
			tbody
				tr
					th 処理履歴
					td#targetText1
		button.btn.brandia_btn#targetButton 非同期処理を開始
JavaScript
const targetText1 = document.getElementById("targetText1");
const targetButton = document.getElementById("targetButton");


// 非同期処理をさせる関数をまとめた配列を作成します。
const arrFunc = [];

//非同期処理させたい関数を先ほどの配列に順番に追加します。今回は学習目的なのでfor文で同じ処理を何個も入れます。
for (let i = 0; i < 20; i++) {
  const func = (resolve) => {
		let additionalElement = document.createElement("div");
		additionalElement.textContent = `処理${i}を開始 ${new Date().toLocaleTimeString()}`;
		targetText1.appendChild(additionalElement);
		
    // 0〜2秒ぐらいで終了判定を遅延させます。今回は学習目的なので処理と処理の間隔をバラバラにすることで、処理が終了する順番にズレが生じるようにしています。
		// ただし、今回はasync awaitを後述しているので、最終的な処理結果は順番通りになります。
    const delayMsec = 2000 * Math.random();

    // 遅延処理
    setTimeout(() => {
			let additionalElement = document.createElement("div");
			additionalElement.textContent = `処理${i}が完了 ${new Date().toLocaleTimeString()}`;
			targetText1.appendChild(additionalElement);
      resolve();
    }, delayMsec);
  };
  // 配列に保存
  arrFunc.push(func);
}

	async function addPromiseAll() {
		// 関数を含めた配列を、Promiseの配列に変換し、それぞれを非同期処理で開始
		for (let i = 0; i < arrFunc.length; i++) {
			const func = arrFunc[i];
			await new Promise(func);
		}
		// 配列に格納された全ての非同期処理を完了した時の処理
		const additionalElement = document.createElement("div");
		additionalElement.textContent = 'すべての処理が完了しました';
		targetText1.appendChild(additionalElement);
	}

//非同期処理開始ボタンの設置
targetButton.addEventListener( 'click', () => {
	addPromiseAll();
});
インストラクター