JavaScript Promiseについての初学者向け解説 - 概念と操作の基礎
本日はJavaScriptのPromiseの操作とその概念について初学者様に解説致します。
前提の知識とスキルとして、少なくともES6の関数・高階関数・コールバック関数およびJavaScriptオブジェクトに精通している必要があります。
上記を学び終えてない方は、以下で学習できますので参照ください。
非同期関数
JavaScriptにおいて非同期処理を行う際には、従来はコールバック関数を利用していました。
しかし、JavaScriptにおいて関数が第一級オブジェクトであるため、コールバック関数を用いることができます。
第一級オブジェクトとは、簡単に言えば変数に代入したり、引数や戻り値として扱うことができるオブジェクトのことです。
JavaScriptにおいては、関数が第一級オブジェクトであるため、関数を引数として別の関数に渡すことができます。
この特性を利用して、コールバック関数を実現しています。
しかし、コールバック関数を不適切に扱うと、コードが読みにくくなる「コールバック地獄」や「スパゲッティコード」に陥ることがあります。
これは、コールバック関数が入れ子になり、エラー処理が煩雑になることが原因です。
そこで、ES6からはPromiseオブジェクトが導入され、非同期処理をより適切に扱うことができるようになりました。
Promiseオブジェクトは、ネストされたコールバック関数を簡潔に書くことができ、エラー処理も明確に行えるため、コードの可読性が向上し、バグの発生を防ぐことができます。
Promise
JavaScriptコミュニティがPromise(約束)を発明したわけではなく、1976年には既にDaniel P. FriedmanとDavid Wiseによって提案され、後にPeter Hibbardによって呼ばれるようになったことが知られています。
彼らはコンピュータサイエンスの教授であり、プログラミング言語を研究していました。
約束とは、実際の生活での約束と同じように、将来何かをすることを保証するものです。
JavaScriptでのPromiseも同じであり、約束の時間が来たときに解決されるか、または拒否されます。
Promiseを使用すると、非同期のリクエストが完了するまでコードブロックの実行を延期できるため、他の操作を中断することなく実行し続けることができます。
これにより、コールバック地獄を回避し、非同期関数を簡単にチェーンできるようになります。
このようにPromiseは、操作の非同期結果を処理するために使用され、if文とは異なります。
Promiseは将来使用できるデータを取得することを約束し、非同期関数の実行を遅延させることができます。
最も重要な利点は、Promiseを使用することで、非同期関数をより簡単にチェーンできることです。
これにより、コードの可読性が向上し、開発者がより生産的になることができます。
Promiseの状態
Promiseはオブジェクトであり、3つの状態が存在します。
最初は「Pending」状態であり、Promiseが成功または失敗する前の初期状態となります。
これは、作成されてから情報を受信するまでの待機時間を表します。
次に、「Resolved」状態に移行します。
これは、Promiseが成功し、処理が完了した状態を表します。
サーバーからデータを受信し、正常に処理が完了した場合、Promiseはこの状態になります。
もし情報の取得が失敗した場合は、「Rejected」状態に移行します。
Promiseが失敗し、エラーがスローされます。この状態では、約束が果たせなかったことを示します。
Promiseを使用して、サーバーからデータを要求する場合、Promiseオブジェクトは最初はPending状態となります。
サーバーは、データを受信するまで保留状態「Pending」になります。
その後、情報が正常に取得された場合はResolved状態に移行し、データを処理することができます。
しかし、情報の取得に失敗した場合は、Rejected状態に移行し、エラーをスローします。
これらの状態は非常に重要であり、Promiseの理解に必要不可欠です。
これらの状態を理解した上で、Promiseオブジェクトを正しく作成することが重要です。
Promiseの作成
JavaScriptで非同期処理を実行する際にPromiseオブジェクトを使うことがあります。
Promiseオブジェクトは、処理が成功したか失敗したかを返し、後続の処理を制御する機能を持っています。
Promiseオブジェクトを作成するには、コンストラクターを使用します。
以下のように、new Promise()でPromiseオブジェクトのインスタンスを作成します。
const myPromise = new Promise((resolve, reject) => { // 非同期処理を行う const condition = true; if (condition) { // 処理が成功した場合、resolveを呼び出す setTimeout(() => { resolve("成功しました!"); }, 300); } else { // 処理が失敗した場合、rejectを呼び出す reject("失敗しました!"); } });
Promiseオブジェクトは、2つの引数を持ちます。
resolveは処理が成功した場合に呼び出され、rejectは処理が失敗した場合に呼び出されます。
Promiseオブジェクトが作成された時点では、まだ処理が実行されていないため、状態は「Pending」になります。
Promiseオブジェクトの状態が変化した時に、then()メソッドとcatch()メソッドを使って後続の処理を行います。
myPromise .then((successMsg) => { // 処理が成功した場合に実行される console.log("成功:" + successMsg); }) .catch((errorMsg) => { // 処理が失敗した場合に実行される console.log("失敗:" + errorMsg); });
then()メソッドは、Promiseオブジェクトがresolveされたときに呼び出されます。
catch()メソッドは、Promiseオブジェクトがrejectされたときに呼び出されます。
このように、Promiseオブジェクトを使うことで、非同期処理の制御が簡単になります。
See the Pen JSのPromise チュートリアル by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
Promise条件をtrueからfalseにし、実行してみて下さい。
情報は取得されず、rejectが呼び出され状態はRejectedに移行しcatch()が実行されエラー文を通知します。
const condition = false; //rejectが呼び出される else { reject('Promiseが失敗、拒否されました!'); } ↓ // 状態はRejectedとなる //catchが実行される .catch((errorMsg) => { alert("Error:" + errorMsg); })
Promiseは一度だけ解決または拒否できるため、上記は機能します。初回resolveまたはreject呼び出しは、Promiseの結果を決定し、別の要求が終了した後に戻ってくる要求によって引き起こされた、それ以降の呼び出しは無視されます。
つまりresolveとrejectは状態がPendingから移行されてしまったら、一度しか実行しません。
他の例でも見てみましょう。
JavaScriptのPromiseは、非同期操作を扱うためのオブジェクトであり、その操作が完了した場合に成功(resolve)または失敗(reject)として解決します。
一度解決または拒否されると、その後の呼び出しは無視されます。
例えば、夕食の約束を例にとると、Promiseを作成して夕食に行くことができるかどうかを判断します。
このPromiseが実行されると、夕食に行くことができる場合にはresolveを、行けない場合にはrejectを呼び出して、その結果を提供します。そして、それに基づいてPromiseの状態をPending状態からFulfilledまたはRejected状態に変更します。
以下は、上記のPromiseをJavaScriptのコードとして示したものです。
const promiseDinner = new Promise((resolve,reject) => { goingToDinner = true; if(goingToDinner){ resolve('夕食に行きます'); }else{ reject('約束が破られた'); } });
上記のPromiseを呼び出すと、thenメソッドが成功した場合に実行され、catchメソッドが失敗した場合に実行されます。
それぞれのメソッドの中に、Promiseの状態に基づいて処理するコードを記述します。
promiseDinner .then((result) => { console.log(result); // 出力: 夕食に行きます }) .catch((error) => { console.log(error); // 出力: 約束が破られた });
このように、Promiseは非同期操作の最終的な完了または失敗を表すオブジェクトであり、その状態に応じてthenメソッドとcatchメソッドで処理することができます。
Promiseは、非同期処理をより簡単に扱うことができるようになりました。
Promiseをチェーンする方法
Promiseの最も重要な利点は、複数の依存する非同期操作を処理するためのチェーンを簡単に作成できることです。
複数の非同期リクエストを順番に実行する必要がある場合、このチェーンは非常に役立ちます。
最初のPromiseが解決または拒否された後、別のPromiseを作成し、これらのPromiseをチェーンすることで、新しいプロセスを開始することができます。
Promiseは、then()メソッドを使用して、複数の処理を順番に実行することができます。
JavaScriptのthen()メソッドは、Promiseオブジェクトにハンドラー関数を追加するために使用されます。
.then()
の結果は、ハンドラーチェーンを通過します。
つまり、ハンドラーが値を返すと、それが次の.then()
に繋がります。
以下は、単純な乗算(掛け算)を.then()
で繋げたチェーンの例です。
const num = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 100); }); num.then((result) => { alert(result); // 1 return result * 3; }) .then((result) => { alert(result); // 3 return result * 5; }) .then((result) => { alert(result); // 15 return result * 6; });
See the Pen JSのPromise メソッドチェーン by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
このように、Promiseの結果は.then()
メソッドに渡すことができます。
この.then()
の方法は、一般的にPromiseチェーンと呼ばれます。
ただし、注意点として、複数回.then()
を呼び出した場合、チェーンにはなりません。
num.then((result) => { console.log(result); // 1 return result * 3; }) num.then((result) => { console.log(result); // 1 return result * 5; }) num.then((result) => { console.log(result); // 1 return result * 6; });
1つのPromiseに対して複数のハンドラーが存在する場合、これらは独立して実行されます。
つまり、チェーンにはならないことに注意してください。
Promiseを返す
Promiseオブジェクトのthen()メソッドには、新しい値を返すことができ、これによって新しいPromiseオブジェクトが即座に解決されるようになります。
以下のコードでは、Promiseオブジェクトを生成し、1秒後に"Hi, "
という値で解決します。
その後、then()メソッドによって、前のPromiseオブジェクトが解決された結果を取得し、新しいPromiseオブジェクトを生成して返します。
これによって、非同期処理をチェーン化することができます。
const myPromise = new Promise((resolve, reject) => { setTimeout(() => resolve("Hi, "), 1000); }).then((result) => { alert(result); // Hi, return new Promise((resolve, reject) => { setTimeout(() => resolve(result + "my name is "), 1000); }); }).then((result) => { alert(result); // Hi, my name is return new Promise((resolve, reject) => { setTimeout(() => resolve(result + "Taro."), 1000); }); }).then((result) => { alert(result); // Hi, my name is Taro. });
上記のコードでは、最初のthen()メソッドで"Hi,"
という値を表示し、その後1秒後に"Hi, my name is "
という値を表示する新しいPromiseオブジェクトを生成し、次のthen()メソッドに渡します。
同様に、2番目のthen()メソッドでは、さらに1秒後に"Hi, my name is Taro."
という値を表示する新しいPromiseオブジェクトを生成し、次のthen()メソッドに渡します。
これによって、一連のタスクを順番に実行し、最終的に"Hi, my name is Taro."
という値を表示することができます。
See the Pen JSのPromise メソッドチェーン Promiseを返す by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
promiseのthenとfinallyの違い
Promiseを学ぶ際に、必ず知る必要があるメソッドの1つがfinally()です。
finally()メソッドは、ES2018で導入されました。
Promiseが解決または拒否された場合、finally()メソッドで指定されたコールバック関数が呼び出されます。
つまり、finally()メソッドはPromiseを返し、Promiseが正常に実行されたか、Promiseが処理された後に拒否されたかに関係なく、コードを実行する方法を提供します。
以下はfinally()メソッドを使用した例です。
new Promise( (resolve) => setTimeout(resolve("成功!"), 1000) ).then(value => console.log(value)).finally(() => console.log("Promiseが正常に成功")) // 出力: 成功! // 出力: Promiseが正常に成功
上記のコードでは、Promiseが正常に処理されているため、finally()メソッドが成功時に実行されています。
次に、失敗した場合を見てみましょう。
new Promise(() => undefined.get()) .then(value => console.log(value)) .catch(error => console.log("失敗!")) .finally(() => console.log("Promiseが正常に成功")) // 出力: 失敗! //出力: Promiseが正常に成功
See the Pen JSのPromise メソッドチェーン by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
上記のコードでは、Promiseが失敗しているため、finally()メソッドが失敗時に実行されています。
重要な点として、成功か失敗かにかかわらず、Promiseはfinally()メソッドを実行します。
これは、Promiseチェーンでエラーのキャッチを延期して、他の場所でエラーを処理する必要がある場合に使用することができます。
ただし、then()メソッドの代替としてfinally()メソッドを使用することは避けることをお勧めします。
finally()メソッドは、タイムアウトのクリア、参照の無効化、UI状態のリセットなど、クリーンアップに使用することができます。
以下はクリーンアップに関する例です
new Promise((resolve, reject) => reject(0)) .catch(x => { console.log(x); // 0 throw x; }) .then(x => { console.log(x); // 動作処理なし }) .finally(() => { console.log("clean up"); // 'clean up' });
finally()メソッドは、他のメソッドと同様にコールバックを受け取りますが、Promiseが解決する値にはアクセスできません。
そのため、値を変更することはできません。
また、finally()メソッドはPromiseチェーンでエラーを処理するために使用されることはありません。
代わりに、catch()メソッドを使用してエラーをキャッチすることが推奨されます。
finally()メソッドは、Promiseが成功した場合でも失敗した場合でも実行され、そのため、Promiseの状態がどうであれ、最後に必ず実行されます。
これは、Promiseチェーンのクリーンアップに役立ちます。例えば、ネットワークリクエストのタイムアウトをクリアする、参照を解放する、UI状態をリセットする、などのクリーンアップを行うことができます。
また、finally()メソッドは、Promiseが成功または失敗した場合に実行されるため、Promiseチェーン内で使用することで、共通のクリーンアップ処理を一箇所にまとめることができます。
function fetchData() { return fetch('https://example.com/data') .then(response => { console.log(response); return response.json(); }) .catch(error => { console.error(error); throw error; }) .finally(() => { console.log('データの取得が完了しました。'); }); }
この例では、fetch()メソッドを使用してデータを取得し、取得に成功した場合はJSONデータを返します。
取得に失敗した場合はエラーをスローします。
どちらの場合でも、finally()メソッドが実行され、データの取得が完了したことが確認できます。
より高度な複数のPromise処理
Promiseコンストラクターオブジェクトには、非同期処理をより高度に制御するための様々なメソッドが含まれています。
その中でも、単一や複数のPromise処理をする為のメソッドとして、Promise.all()、Promise.allSettled()、Promise.race()、Promise.any()があります。
ここでは、Promise.all()メソッドについて詳しく説明します。
Promise.all()メソッドは、引数に渡された複数のPromiseが全て解決(resolve)した時に、新たなPromiseを返します。
この新たなPromiseは、全てのPromiseの値を配列にして解決され(resolve)ます。
例えば、以下のようにPromise.all()を使用することで、3つの非同期処理を行い、それらが全て終了した時に結果をまとめて受け取ることができます。
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('最初の約束が解決しました'); resolve(1); }, 1 * 100); }); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('2つ目の約束が解決しました'); resolve(3); }, 2 * 100); }); const promise3 = new Promise((resolve, reject) => { setTimeout(() => { console.log('3つめの約束が解決しました'); resolve(5); }, 3 * 100); }); Promise.all([promise1, promise2, promise3]).then(result => { console.log(`Result: ${result}`); }); // 出力: 最初の約束が解決しました // 出力: 2つ目の約束が解決しました // 出力: 3つめの約束が解決しました // 出力: Result: 1,3,5
See the Pen JSのPromise finallyメソッド by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
Promise.all()は、非同期処理を同時に行うことで、複数の結果を効率的に取得することができます。
また、Promise.all()は、全てのPromiseが解決された場合にのみ解決されるため、どれか一つでも失敗した場合はエラーとなります。
これを使えば、複数のAPIからデータを取得し、それらをまとめて表示するなど、様々な用途に応用することができます。
JavaScriptのFetchAPIを使用したPromiseベースのHTTPリクエスト
当記事ではPromiseを作成し、結果を処理しました。
Promiseは、非同期の操作が完了した後に結果を返すためのオブジェクトです。
Fetch APIを使用すると、HTTPリクエストを行った後にPromiseオブジェクトが返されます。
以下のコードを見てみましょう。
const getUsers = fetch('https://jsonplaceholder.typicode.com/users') .then(response => { if (response.ok) { console.log ("Request 成功"); return response } else { console.log("Request 失敗"); } }) .then(response => response.json()) .then(data => console.log(data))
上記のコードでは、fetch()メソッドを使用して、「https://jsonplaceholder.typicode.com/users」からのGETリクエストを実行します。
リクエストが成功した場合、response
オブジェクトが返されます。
Promiseチェーンの最初のthen()
メソッドでは、response.ok
をチェックして、レスポンスが正常な場合はそのままresponse
オブジェクトを返し、失敗した場合はエラーを処理します。
次に、response.json()メソッドを使用して、JSONデータを取得し、then()メソッドでそのデータを処理しています。
最後に、JSONデータをコンソールに出力しています。
このように、Promiseを使用すると、非同期処理を行う際にコールバック地獄を避けることができます。
Promiseチェーンを使用すると、処理が一連の手順に分かれ、可読性の高いコードを書くことができます。
最後に
JavaScriptにおいて、then()メソッドとcatch()メソッドを使用することで、Promiseを簡単にチェーンすることができます。
then()メソッドは、追加されたPromiseが実行された時に行うべき操作を定義するために使用されます。
一方、catch()メソッドは、Promiseが拒否された場合に処理することができます。
過去には、コールバック地獄という問題が存在しましたが、Promiseの導入によりこの問題が大幅に軽減されました。
ただし、この手法に大きく依存することが必要です。
また、ES2017以降では、async/awaitを使用することで、よりクリーンなコードを記述することができます。
async/awaitは、Promiseを操作するための新しい構文であり、Promiseチェーン手法よりも優れています。
当ブログでは、これについて詳しく解説していますので、是非参照し次のステップに移行してみてください。
以上が、then()メソッドとcatch()メソッドを使用してJavaScriptでPromiseをチェーンする手順およびPromise概念の解説です。
この記事が役に立った場合は、ブックマークや共有をお願い致します。