JavaScriptは基本的にシングルスレッドで実行されるため、長時間かかる処理やI/O待ちの処理を行う際には非同期処理の仕組みが重要になります。ES6で標準化されたPromiseオブジェクトは、非同期処理の完了や失敗を表現するためのオブジェクトです。Promiseを使うと、コールバック関数地獄(いわゆる「Callback Hell」)を回避し、読みやすい非同期コードを書くことができます。
Promiseオブジェクトは最初は「保留(pending)」状態ですが、処理が完了すると「fulfilled(履行)」状態になり結果の値を保持します。エラーが起きれば「rejected(拒否)」状態になりエラー理由を保持します。Promiseには.then()
メソッドと.catch()
メソッドが用意されており、成功時の処理と失敗時の処理をそれぞれチェーン(連結)して書くことが可能です。
本章では、Promiseの基本的な使い方として、新たにPromiseを作成する方法と、既存の非同期関数から返されるPromiseを扱う方法を学びます。
JavaScriptでPromiseを使用する例を示します。fetchData
という擬似的な非同期関数を作り、1秒後にデータを返すようにしてみます。そのPromiseを.then
で受け取り、結果を処理します。
function fetchData() {
return new Promise((resolve, reject) => {
console.log("データ取得中...");
setTimeout(() => {
const success = true; // 成功/失敗を決めるフラグ
if (success) {
resolve({ message: "データ取得成功", data: [1, 2, 3] });
} else {
reject(new Error("データ取得失敗"));
}
}, 1000);
});
}
// Promiseを利用した非同期処理の実行
fetchData()
.then(result => {
console.log(result.message); // "データ取得成功"
console.log(result.data); // [1, 2, 3]
})
.catch(error => {
console.error(error); // エラー時はこちらが実行される
})
.finally(() => {
console.log("非同期処理が完了しました。");
});
解説:
fetchData
関数ではnew Promise(...)
を返しています。コンストラクタに渡した関数は「executor(実行者)」と呼ばれ、resolve
とreject
という2つのコールバックを引数に取ります。非同期処理が成功した際にはresolve(結果)
を呼び出し、失敗した際にはreject(エラー)
を呼び出します。上記ではsetTimeout
で1秒待った後、success
フラグに応じて成功ならオブジェクトをresolve
し、失敗ならエラーメッセージをreject
しています。
fetchData()
の呼び出し側では、返されたPromiseに対して.then()
で成功時の処理、.catch()
で失敗時の処理、.finally()
で最終的に常に行いたい処理を指定しています。.then()
の中のresult
はresolve
で渡された値(ここでは{ message: ..., data: ... }
オブジェクト)が渡ってきます。一方.catch()
のerror
にはreject
で渡されたError
オブジェクトが渡ります。.finally()
は成功・失敗に関わらず一度だけ実行されるブロックですので、後処理などに利用できます。
なお、Promiseチェーンでは.then
を続けて書くことで非同期処理の結果を次の.then
に渡していくこともできます。例えば.then(result => { return 加工した新しい結果; })
のようにreturn
すると、新たなPromiseとして次の.then
に渡されます。この仕組みにより、非同期処理を順序立てて書き進めることができます。