10.1 Promiseとは

Promise は、非同期処理の「結果(成功または失敗)」を管理するための特別なオブジェクトです。
一言で言うと「非同期処理の結果を約束する仕組み」です。

例えば、前章の最初で、サーバーから大量のデータを取得するなど、すぐに結果が出ない処理に対して、Promise は「成功したか」「失敗したか」を後から通知してくれます

この荷物の配達に例えられる3つの状態を、以下の言葉で表します。

Pending(待機中)

非同期処理が完了しておらず、処理が完了するのを待っています。いわば、荷物が配達中の状態。

Fulfilled(成功)

非同期処理が終わった後で、処理が正常に終了した状態。配達に成功し、荷物も無事に届いている。

Rejected(失敗)

非同期処理が終わった後で、処理が異常終了した状態(エラー)。配達に失敗し、荷物が届いてない。

10.1.1 Promise オブジェクトを作るプログラム

Promise オブジェクトを作成する際は、コンストラクタ引数として「実行したい処理」を記述した関数を渡します。この関数には、JavaScript から自動的に 2 つの引数 resolve と reject が渡されます。これらは、処理の結果を通知するための 「スイッチ(ボタン)」 だと考えてください。

まずは、ランダムな数値によって成功(resolve)と失敗(reject)が変わるシンプルな例を見てみましょう。

ソースフォルダ:public/ch10

ファイル名:promise-basic.js

➢ promise-basic.html

前の章で作った ch09/callback-hell.html をコピーし、ch10 にペーストしてください。
その際に、ファイル名を promise-basic.html に変更します。
ファイル内の callback-hell.html や callback-hell.js の記述も、promise-basic.html や promise-basic.js に修正します。

➢ promise-basic.js

// promise-basic.js

// 1. Promise(約束)の作成
const myPromise = new Promise((resolve, reject) => {
	// 成功か失敗かをランダムに決める
	const isSuccess = Math.random() > 0.5;

	if (isSuccess) {
		// 成功通知
		resolve("成功しました!"); 
	} else {
		// 失敗通知
		reject("失敗しました…(エラー発生)");
	}
});

// 2. 結果の受け取り
myPromise
	.then((message) => {
		// resolveされた場合に動き、アラートを出す
		alert(`【結果】${message}`); 
	})
	.catch((errorMessage) => {
		// rejectされた場合に動き、エラーログを出す
		alert(`【結果】${errorMessage}`);
    })
	.finally(() => {
		// 最後に必ず実行する
		alert("処理が完了しました。");
	});

実行結果

解説

このプログラムは 2 つのセクションで成り立ってます。
前半の 4 行目から 15 行目では、myPromise という名前で新しい Promise オブジェクトを生成しています。

ここでは仮に 6 行目の Math.random()で生成させた乱数が 0.5 より大きいか小さいかを元に、Promise の結果が resolve(処理成功)か reject(処理失敗)かを決めるようにしています。

6: const isSuccess = Math.random() > 0.5;

乱数の結果が 0.5 より大きい場合:resolve(処理成功)として”成功しました!”の値を返す。

10: resolve("成功しました!");

乱数の結果が 0.5 より小さい場合:reject(処理失敗)として”失敗しました…(エラー発生)”の値を返す。

13: reject("失敗しました…(エラー発生)");

この前半部で判定された結果を元に、後半の 18 行目から 26 行目で処理を分岐します。

Promise オブジェクトは、前半部の結果判定が終わった後で resolve と reject の結果に対応して自動的に呼び出される、以下の 3 つのメソッドを持っています。

18 行目で、myPromise を呼び出した後、まるで数珠つなぎの様に.then() と.catch() が並んでいます。
それぞれのアロー関数の先にあるのが、resolve だった時に動くメソッドの処理の中身と、reject だった時に動くメソッドの処理の中身です。

myPromise の状態が確定すると、結果に該当するメソッドだけが実行され、最後に必ず.finally が実行されます。このような.(ドット)で改行してメソッドを書くスタイルをメソッドチェーンと言います。

また、わざわざ結果が reject だった場合のメソッドを .catch() で定義しているのには意味があります。

実務での非同期処理(通信など)では、必ず結果が成功するとは限りません。「サーバーがダウンしている」「通信が切れた」といったトラブルが起きた際、プログラムをフリーズさせずに「エラーが起きた」ことを正しく通知するために reject が存在します。

これにより、.catch() から始まるメソッドの中で、安全にエラー対応(アラートを出すなど)を行うことができます。

10.1.2 Promise とメソッドチェーンでコールバック地獄を解消するプログラム

Promise を使うと、前章で学んだ「コールバック地獄」をどのように改善できるか確認しましょう。
複数の処理をメソッドチェーンにすることで、コードのネスト(入れ子)を深くせずに記述することができます。

ソースフォルダ:public/ch10

ファイル名:delay2.js

➢ delay2.html

前の項で作った ch10/promise-basic.html をコピーし、ch10 にペーストしてください。
その際に、ファイル名を delay2.html に変更します。ファイル内の promise-basic.html や promise-basic.js の記述も、delay2.html や delay2.js に修正します。

➢ delay2.js

// delay2.js

// 指定した秒数待機する関数「delay」を定義します
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 1秒ごとにメッセージを出します
delay(1000)
   .then(() => {
       alert("1秒経過:ステップ1完了");
       return delay(1000); // 次のステップのためのPromiseを返す
   })
   .then(() => {
       alert("さらに1秒経過:ステップ2完了");
   });

実行結果

解説

今回のサンプルでは、4~6 行目で定義されている delay という function がポイントになります。
戻り値として、新たな Promise オブジェクトを生成し、その先で呼び出した setTimeout 関数の引数として ms で与えられた時間が経過したら、コールバック関数として resolve を実行させています。

これによって、Promise オブジェクトは、指定時間経過後に .then() を常に実行するようになります。

4: function delay(ms) {
5:     return new Promise(resolve => setTimeout(resolve, ms));
6: }

仮に、これを省略せずに書くと、このようになります。

4: return new Promise(resolve => {
5:     setTimeout(() => {
6:     // 指定した時間(ms)が経過したら、ここで resolve() を実行する
7:         resolve();
8:     }, ms);
9: });

コールバック関数の記述が簡単になるのが、アロー関数の良いところでもありますが、このような場合には、逆に関数なのかわかりづらくなりますので、慣れないうちは注意しましょう。