JavaScriptの非同期処理についてまとめてみた②
こんにちは!
スマレジ テックファームのMichiです!
この記事は、『JavaScriptの非同期処理についてまとめてみた①』の続きになります。
前回のおさらい
さて、前回はsetTimeout
関数を利用して、「5、4、3、2、1、0」のように1秒ずつカウントダウンする処理を作った結果、地獄のようなコードが生まれたのでした。
console.log('開始') setTimeout(() => { console.log(5); setTimeout(() => { console.log(4); setTimeout(() => { console.log(3); setTimeout(() => { console.log(2); setTimeout(() => { console.log(1); setTimeout(() => { console.log(0); }, 1000); }, 1000); }, 1000); }, 1000); }, 1000); }, 1000);
これを「コールバック地獄」と呼ぶのでしたね。
そして、このコールバック地獄を解決する方法が、Promise
とasync/await
だというところまで、前回の記事で書きました。
今回は、Promise
とasync/await
の具体的な使い方を解説していきたいと思います。
Promise
完成形
小難しい話をしてもイメージが湧かないと思うので、まずは完成形からお見せします。
/* 非同期処理を関数として定義 ... ① */ const countDown = (num) => { // Promiseオブジェクトを生成して、戻り値として返す ... ② return new Promise((resolve, reject) => { setTimeout(() => { if (typeof num === "number") { console.log(num) resolve(); // 処理の成功を通知する ... ③ } else { reject("整数を入力してください"); // 処理の失敗を通知する ... ③' } }, 1000); }); }; /* 実行 */ console.log('開始'); countDown(5) // 処理に成功した時、次のcountDownを呼び出す ... ④ .then(() => countDown(4)) .then(() => countDown(3)) .then(() => countDown(2)) .then(() => countDown(1)) .then( () => countDown(0), // 処理に失敗した時、エラーメッセージを出力 ... ④' (error) => console.log(error) );
Promise
を使用すると、first().then(second).then(third)...
というように、then
で繋げることによって1本道のコードで記述できるようになります。直観的でわかりやすいコードになりましたね!
では、Promise
の使い方を解説します。
解説
Promise
を利用するには、まず非同期処理を関数としてまとめておきます(①)。
countDown
関数が戻り値として返すのが、Promise
オブジェクトです(②)。
Promiseコンストラクター
new Promise((resolve, reject) => { statements }) resolve: 処理の成功を通知するための関数 reject: 処理の失敗を通知するための関数 statement: 処理本体
引数のresolve
/reject
は、それぞれ非同期処理の成功と失敗を通知するための関数です。
成功時はresolve
メソッドを、失敗時はreject
メソッドを呼び出し、結果をthen
に引き渡します。例では、成功時はコンソールに値を表示してから処理成功の通知を行っています(③)。失敗時はreject
の引数にエラーメッセージを渡すことで、処理失敗とエラーメッセージの通知を行います(③')。
resolve
/reject
の通知内容を受け取るのは、then
メソッドの役割です。
thenメソッド
promise.then(success, failure) promise: Promiseオブジェクト success: 成功コールバック関数(resolve関数によって呼び出し) failure: 失敗コールバック関数(reject関数によって呼び出し)
例では、非同期処理に成功したとき、次のcountDown
を呼び出しています(④)。失敗時はreject
から受け取ったエラーメッセージをコンソールへ出力します(④')。
ちなみに、countDown(5)
~countDown(1)
の中では、失敗時のコールバック関数を渡していませんが、その場合、該当のthen
メソッドはスキップされて、最後(countDown(0)
)の失敗コールバックが呼び出されます。このように、then
メソッド単位に失敗コールバックを定義せずに、必要な箇所でまとめてエラー処理できるのも、Promise
のいいところです。
でも、まだちょっと分かりにくくない…?
最初のコールバック地獄よりはだいぶマシになりましたが、Promise
は書き方が特殊でまだちょっと分かりにくいような気もします。
そこで登場するのが、ES2017から導入されたasync/await
という書き方です。
async/await
完成形
これも先に完成形からお見せしましょう。
/* 非同期処理を関数として定義 ... ① */ const countDown = (num) => { // Promiseオブジェクトを生成して、戻り値として返す ... ② return new Promise((resolve, reject) => { setTimeout(() => { if (typeof num === "number") { console.log(num); resolve(); // 処理の成功を通知する ... ③ } else { reject("整数を入力してください"); // 処理の失敗を通知する ... ③' } }, 1000); }); }; /* 実行したい処理をasync関数の中で書く ... ④ */ const execute = async () => { console.log("開始"); await countDown(5); await countDown(4); await countDown(3); await countDown(2); await countDown(1); await countDown(0); }; /* 実行 */ execute();
await
はPromise
オブジェクトを同期的に処理する機能です。await
を書くことによって、先ほどのthen
のときより、より直感的に同期処理を行っているということが分かると思います。
解説
まず、await
を使用するには条件があります。それは、async
が付いた関数の中でしか利用できないということです(④)。
次に、同期処理を行いたいPromise
オブジェクトをasync
関数の中で呼び出し、その前にawait
キーワードを付与します。await
を付けることによって、Promise
が確定しその結果を返すまで、JavaScriptを待機させるという処理になります。 非常に簡単ですね!
ちなみに、async
関数の中でreject
(処理の失敗)を受け取りたい場合は、try/catch
が使用できます。
try { await countDown("hoge"); } catch (e) { console.log(e); } // 結果:整数を入力してください
まとめ
前回、今回に渡り、JavaScriptの非同期処理についてまとめました。
特にPromise
については、JS初心者には分かりにくかったかもしれませんが、JSを扱う上で非常に大切な概念なので、しっかりマスターしておきましょう!