JavaScript Promises
1. なぜ Promise なのか?
JavaScript プロミス(Promises)は、非同期(Asynchronous)処理をより簡単に扱うために導入されました。
Promise オブジェクトは、非同期操作の「完了」または「失敗」を表します。
多くのコールバック(Callbacks)を使用すると、コードの可読性が低下し、メンテナンスが困難になります。
例(コールバック地獄)
step1(function(r1) {
step2(r1, function(r2) {
step3(r2, function(r3) {
console.log(r3);
});
});
});このような記述スタイルは、しばしば「コールバック地獄(Callback Hell)」と呼ばれます。
Promise を使用することで、同じロジックをよりクリーンに記述できます。
Promise は、将来のある時点で利用可能になる値のプレースホルダー(Placeholder)として機能し、従来のコールバックよりも洗練された方法で非同期コードを処理することを可能にします。
2. Promise の状態 (Promise States)
Promise は、以下の3つの排他的な状態のいずれかを取ります。
- Pending(ペンディング): 初期状態。操作が開始されたが、まだ完了していない。
- Fulfilled(フルフィルド): 操作が正常に完了し、値が利用可能な状態。
- Rejected(リジェクテッド): 操作が失敗し、エラー理由が利用可能な状態。
Promise が Fulfilled または Rejected になった場合、その Promise は「Settled(セットルド / 確定)」したとみなされます。
3. Promise の作成 (Creating a Promise)
let myPromise = new Promise(function(resolve, reject) {
// 時間のかかる処理をここに記述
resolve(value); // 成功時
reject(value); // エラー発生時
});Promise コンストラクタは、2つの引数(Parameters)を持つ関数を受け取ります。
| 引数 | 説明 |
|---|---|
| resolve | 処理が正常に終了したときに実行する関数 |
| reject | エラーで終了したときに実行する関数 |
4. Promise の使い方
Promise を利用(コンシューム)する方法は以下の通りです。
myPromise.then(
function(value) { /* 成功時のコード */ },
function(value) { /* 失敗時のコード */ }
);.then() は2つの引数を取ります。1つは成功時、もう1つは失敗時のコールバック関数です。
これらは両方ともオプションであるため、成功時のみ、あるいは失敗時のみのコールバックを追加することも可能です。
実装例
// Promise オブジェクトの作成
let myPromise = new Promise(function(resolve, reject) {
let ok = true;
// 時間のかかる処理
if (ok) {
resolve("OK");
} else {
reject("Error");
}
});
// then() を使用して結果を表示
myPromise.then(
function(value) { myDisplayer(value); },
function(value) { myDisplayer(value); }
);Promise は将来利用可能になる値を表すコンテナであり、その結果は「値」または「エラー」のいずれかになります。
5. JavaScript Promise オブジェクト
Promise オブジェクトには、値を生成する「Producing Code(生成コード)」と、それを呼び出す「Consuming Code(消費コード)」の両方が含まれます。
5.1 Promise の構文構造
let myPromise = new Promise(function(resolve, reject) {
// "Producing Code" (時間がかかる可能性がある処理)
resolve(value); // 成功時
reject(value); // 失敗時
});
// "Consuming Code" (Promise が確定するのを待機)
myPromise.then(
function(value) { /* 成功時の処理 */ },
function(value) { /* 失敗時の処理 */ }
);生成コードが結果を得たとき、以下のいずれかのコールバックを呼び出す必要があります。
| 状況 | 呼び出す関数 |
|---|---|
| 成功 (Success) | resolve(value) |
| 失敗 (Error) | reject(value) |
Promise が resolve または reject されるのは、一度きりです。
5.2 Promise オブジェクトのプロパティ
Promise オブジェクトは、内部的に state(状態)と result(結果)という2つのプロパティをサポートしています。
| myPromise.state | myPromise.result |
|---|---|
| "pending" | undefined |
| "fulfilled" | 結果の値 (result value) |
| "rejected" | エラーオブジェクト (error object) |
state や result プロパティに直接アクセスすることはできません。Promise を扱うには、必ず Promise メソッドを使用する必要があります。
6. コアメソッドと使用方法
Promise オブジェクトに付随するメソッドを使用して、結果を処理します。
.then(onFulfilled, onRejected): 成功時と失敗時両方のハンドラを登録します。新しい Promise を返すため、メソッドチェーン(Method Chaining)が可能です。.catch(onRejected): .then(null, onRejected)のショートハンドであり、通常、チェーンの最後でエラーを捕捉するために使用されます。.finally(onFinally): 成功・失敗に関わらず、Promise が確定したときに必ず呼び出されます。クリーンアップ処理に有用です。
6.1 then と catch の使用例
let promise = Promise.resolve("OK");
promise
.then(function(value) {
console.log(value);
})
.catch(function(value) {
myDisplayer(value);
});7. Promise を返す
.then() 内から Promise を返すことで、強力なメソッドチェーンを作成できます。
// ステップ実行する3つの関数
function step1() {
return Promise.resolve("A");
}
function step2(value) {
return Promise.resolve(value + "B");
}
function step3(value) {
return Promise.resolve(value + "C");
}
// ステップごとに実行
step1()
.then(function(value) {
return step2(value);
})
.then(function(value) {
return step3(value);
})
.then(function(value) {
myDisplayer(value);
});7.1 catch の配置場所
エラーハンドリングはチェーンの最後にまとめることができます。1つの .catch() で、それより上のどのステップで発生したエラーも捕捉可能です。
step1()
.then(function(value) {
return step2(value);
})
.then(function(value) {
return step3(value);
})
.catch(function(error) {
console.log(error);
});8. 初心者が陥りやすいミス
Promise を返すのを忘れると、チェーンが壊れてしまいます。
間違った例
step1()
.then(function(value) {
step2(value); // return がない!
})
.then(function(value) {
console.log(value); // 前のステップを待たずに実行されてしまう
});最初の .then() から何も返されないため、2番目の .then() が早すぎるタイミングで実行されてしまいます。非同期ステップを開始する場合は、必ずそれを return してください。
9. 実践的な JavaScript における Promise
多くの Web API が Promise を返します。その代表例が fetch() です。
fetch("data.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.log(error);
});10. Promise API のスタティックメソッド
複数の Promise を一度に扱うためのスタティックメソッド(Static Methods)も用意されています。
Promise.all(iterable): すべての Promise が成功した時に解決します。1つでも失敗すると即座に拒否(Reject)されます。Promise.allSettled(iterable): すべての Promise が確定(成功または失敗)するのを待ち、各結果の配列を返します。Promise.race(iterable): 最も早く確定した Promise の結果を返します。Promise.any(iterable): 最初に成功した Promise の値を返します。すべて失敗した場合のみ拒否されます。
11. タイムアウトの待機
コールバックによる例
setTimeout(function() { myFunction("大好きです!!!"); }, 3000);
function myFunction(value) {
document.getElementById("demo").innerHTML = value;
}Promise による例
let myPromise = new Promise(function(myResolve, myReject) {
setTimeout(function() { myResolve("大好きです!!"); }, 3000);
});
myPromise.then(function(value) {
document.getElementById("demo").innerHTML = value;
});12. ファイルの待機
コールバックによる例 (XMLHttpRequest)
function getFile(myCallback) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.html");
req.onload = function() {
if (req.status == 200) {
myCallback(req.responseText);
} else {
myCallback("Error: " + req.status);
}
}
req.send();
}
getFile(myDisplayer);Promise による例
let myPromise = new Promise(function(myResolve, myReject) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.html");
req.onload = function() {
if (req.status == 200) {
myResolve(req.response);
} else {
myReject("ファイルが見つかりません");
}
};
req.send();
});
myPromise.then(
function(value) { myDisplayer(value); },
function(error) { myDisplayer(error); }
);