PromiseはECMAScript 2015(ES6)で導入されたクラス(new Promise)であり、JavaScriptにおける非同期プログラミングを管理するために設計されました。これにより、いわゆる「Promiseデザインパターン」が実現されています。
Promiseは非同期問題を解決するためのツールです。Promise自体は同期的ですが、非同期プログラミングを管理するためのパターンとして機能します。
簡単に言えば、Promiseはコンテナ(ラッパー)であり、その中には将来終了するイベント(通常は非同期操作)の結果が保存されています。
Promiseオブジェクトはコンストラクタ関数であり、resolve、reject、allといったメソッドを持ちます。プロトタイプ上にはthen、catchなどのメソッドが存在します。
Promiseには3つの状態があります:
- pending:準備状態
- fulfilled:成功状態(resolve)
- rejected:失敗状態(reject)
最終的には2つの状態しかありません:1. 準備中 2. 成功または失敗
const futureData = new Promise((resolve, reject) => {
setTimeout(() => {
const result = '北海道';
resolve(result); // 成功時に値を返す
// reject(new Error("エラーが発生しました")); // 失敗時にエラーを返す
}, 1000);
});
futureData.then(
data => {
console.log(data);
},
error => {
console.log('エラーが発生しました', error);
}
);
/*
1. 非同期操作はPromiseに渡された関数内に記述します
2. Promiseの引数とthenの引数が対応しています
*/
Promiseオブジェクトの特徴
- Promiseオブジェクトの状態は外部からの影響を受けません。非同期操作の結果のみが現在の状態を決定し、他の操作ではこの状態を変更できません
- 一旦状態が変更されれば、それ以上は変化しません。Promiseオブジェクトの状態変更には2つの可能性しかありません:pendingからfulfilledへの変化、またはpendingからrejectedへの変化
Promiseのexecutor(実行関数)は、new Promiseが呼び出された際にデフォルトで即座に実行されます。
各Promiseインスタンスにはthenメソッドがあり、これを使ってresolved状態とrejected状態のコールバック関数を指定します。
thenメソッドは複数のチェーン(連鎖呼び出し)が可能です(ただし、各thenがPromiseインスタンスを返すことが条件です)。
const fetchData = new Promise((resolve, reject) => {
// 非同期操作を実行
const xhr = new XMLHttpRequest();
xhr.open('GET', 'data.json', true);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
const response = xhr.responseText;
resolve(response); // 成功
}
if(xhr.status !== 200) {
reject(new Error("データ取得に失敗しました")); // 失敗
}
}
xhr.send();
})
fetchData.then(
data => {
console.log('データ取得成功');
// データ処理
return data;
},
error => {
console.log('エラーが発生しました', error);
}
).then(() => {
// 最初のthenの処理完了後に実行
console.log('次の処理を実行');
}).then(() => {
// 2番目のthenの処理完了後に実行
console.log('最終処理を実行');
})
Promiseは複数の並行リクエストをサポートしており、並行処理中のデータを取得できます。
Promiseで取得したデータはnullまたは配列の可能性があります。配列の場合はthen()で処理し、nullの場合はthen()を実行する必要はありません。
Promise.prototype.catch()
このメソッドは.then(null, rejection)のエイリアスであり、エラー発生時のコールバック関数を指定するために使用します。
fetch('/api/data.json')
.then(response => response.json())
.then(data => {
// データ処理
console.log(data);
})
.catch(error => {
// getJSONと前のコールバック関数の実行中に発生するエラーを処理
console.log('エラーが発生しました!', error);
})
一般的に、thenメソッド内でreject状態のコールバック関数を定義するのは避けるべきです(つまり、thenの第二引数を使用しないでください)。
成功時の処理はthenで、エラー時の処理はcatchで行うのが推奨されます。
// 非推奨
somePromise
.then(function(data) {
// 成功時の処理
}, function(err) {
// エラー時の処理
});
// 推奨
somePromise
.then(function(data) {
// 成功時の処理
})
.catch(function(err) {
// エラー時の処理
});
Promise.all
Promise.allメソッドは、複数のPromiseインスタンスを新しいPromiseインスタンスでラップするために使用されます。返される各メンバーはPromiseインスタンスです。
すべてのPromiseが成功した場合にthenが実行され、いずれか一つでも失敗すればcatchが実行されます。
Promise.all([promise1, promise2, promise3])
.then(results => {
// すべてのPromiseが成功した場合
console.log('すべての処理が成功しました', results);
})
.catch(error => {
// 少なくとも一つのPromiseが失敗した場合
console.log('一部の処理が失敗しました', error);
});