【JS】非同期処理入門
今回はJavaScriptにおける最も最大のつまずきポイント、非同期処理について解説します。
実務では100%非同期処理を使うことになるので、必ず抑えておきましよう。
なるべく分かりやすく解説していきます。
そもそも非同期処理とは
まずは、同期処理と非同期処理について説明していきます。
同期処理
基本的にjavascriptのコードは、同期処理と呼ばれているものになります。
同期処理ではコードを順番に処理していき、一つの処理が終わるまで次の処理は行いません。
なので、実行している処理は一つだけとなり、とても直感的な動作となります。
しかし、同期処理ではひとつの処理が終わるまで次の処理へ進むことができないので問題があります。
なぜなら、JavaScriptは基本的にブラウザのメインスレッドで実行されるためです。
メインスレッドは表示の更新といったUIに関する処理も行っています。
そのため、何か重たい同期処理があると、表示が更新されなくなりフリーズしたようになります。
料理で例えると、
1.A君が食材をカットする
2.それが終わったらB君が食材を炒める
3.それが終わったらC君がお皿を準備する
と言った感じのものが同期処理になります。
同期処理だと、B君はA君の作業が終わるのを待つ必要が出てきてしまい、C君は二人の作業を待つ必要があります。
と、同期処理は動きが分かりやすいですが、無駄が生まれやすいです。
非同期処理
次に非同期処理です。
非同期処理はコードを順番に処理していきますが、ひとつの非同期処理が終わるのを待たずに次の処理を実行します。
つまり、実行結果を待つ必要がなくなるので、先ほど言ったブラウザがフリーズする問題が解決するのです。
そして、基本的にAPIを使った通信が発生する場合は非同期処理となるので、覚えておきましょう。
先ほどの料理の例えで言うと、
1.B君はA君のカットが終わってようが終わってまいが、処理を実行するタイミングが来たらフライパンで炒める動作を始めます。
2.そして、C君も二人の作業が終わるのを待つことなくお皿の準備を始めます。
確かに待ち時間は無くなったのですが、非同期処理ではB君が何も食材が無い状態で炒め物を始める可能性があります。
このように、非同期処理は便利な文少し扱いが難しいです。
そこで、役に立つのがPromiseとAsync,Awaitです。
非同期処理の仕組み
次に、非同期処理の仕組みについて解説していきます。
コーススタック、コールバックキュー、タスクキューの3つの箱があるのをイメージしておいてください。
まず、JavaScriptを実行するとコールスタックという箱にタスクが積まれていきます。
けれど非同期処理を見つけた場合は、コールバックキューというところにタスクが格納されていきます。
そしてイベントループという機能で、コールスタックにタスクが積まれているかを定期的に確認します。
コールスタックにタスクが積まれていた場合は、タスクキューというところに移動させ、順番に実行していきます。
もしコールスタックが空の場合は、コールバックキューからタスクを取り出してコールスタックに積みます。
そして、コールスタックに積まれたタスクが実行される訳です。
なので、通常の同期処理の実行が終わるまでは、非同期処理の実行は行われません。
非同期処理の書き方
先ほど述べたとおり、非同期処理は若干扱うのが難しいです。
そこで、使われるのがPromiseとAsync,Awaitです。
これを使うと、
A君が食材のカットをする
それが終わったらB君が食材を炒める
そして、二人の作業完了を待つことなくC君がお皿の準備を始める
と言った理想的な動きを実現することができます。
例えだと、イメージはできてもコードがイメージしにくいと思うので、ここからは実際のコードを使って解説していきます。
Promise
Promiseはnew演算子でインスタンスを作成して利用します。
このときresolveとrejectの2つの引数を渡します。
そして、非同期処理が成功した場合はresolve関数が呼ばれ、失敗した場合はreject関数を呼び出します。
と説明しましたが、あまり覚えなくてもOKです。
なぜなら、ほとんどの非同期処理は元からPromiseオブジェクトを返してくれるからです。
例えば、fetchメソッドというAPIの通信で使う関数がありますが、これは返り値にPromiseオブジェクトを返します。
なので、次に実行したい値をthenで繋ぎ、失敗したときの処理をcatchで繋げれば想定通りに動いてくれます。
const promiseFunc = fetch("https://jsonplaceholder.typicode.com/posts/1");
promiseFunc.then((res) => console.log(res)).catch((e) => console.error(e));
先ほどの料理の例えをコードに反映すると
console.log("A君が仕事を始めた");
const promiseFunc = fetch("https://jsonplaceholder.typicode.com/posts/1");
promiseFunc
.then((res) => console.log("A君が仕事を終えてB君が仕事を始めた"))
.catch((e) => console.error(e));
console.log("C君が仕事を始めた");
/* log
A君が仕事を始めた
C君が仕事を始めた
A君が仕事を終えてB君が仕事を始めた
*/
このようになります。
ちなみに、このPromiseが出る前は、関数をコールバックさせて非同期処理を制御していました。
ただ、そのような方法だとコールバックが何回もネストされて読みにくいという問題点がありました。
また、非同期処理の中でエラーが起こった場合は、そのエラーを上手くキャッチすることができないという問題もありました。
そのような背景もあり、Promiseという仕様が誕生したらしいです。
Async,Await
次に、AsyncとAwaitについて説明していきます。
要は、先ほどのPromiseを分かり易くしたものになり、原理は同じです。
Async Functionは通常の関数とは異なり、必ずPromiseインスタンスを返す関数を定義する構文です。
また、Async Function内ではawait式というPromiseの非同期処理が完了するまで待つ構文が利用できます。
await式を使うことで非同期処理を同期処理のように扱えるため、Promiseチェーンで実現していた処理の流れを読みやすく書けます。
非同期関数の返り値は次の3パターンに分かれます。
- Async Functionが値をreturnした場合、その返り値を持つFulfilledなPromiseを返す
- Async FunctionがPromiseをreturnした場合、その返り値のPromiseをそのまま返す
- Async Function内で例外が発生した場合は、そのエラーを持つRejectedなPromiseを返す
なので、先ほどのコードをAsync,Awaitを使って書き換えると以下のようになります。
async function promiseFunc() {
const value = await fetch("https://jsonplaceholder.typicode.com/posts/1");
console.log(value);
console.log("A君が仕事を終えてB君が仕事を始めた");
}
console.log("A君が仕事を始めた");
promiseFunc();
console.log("C君が仕事を始めた");
/* log
A君が仕事を始めた
C君が仕事を始めた
A君が仕事を終えてB君が仕事を始めた
*/
おわりに
今回は、非同期処理について解説ました。
正直、難しいと思うので概要をなんとなく把握して実務で慣れていけばOKです。
最後に宣伝です。
0からエンジニアになるためのノウハウをブログで発信しています。
また、YouTubeでの動画解説も始めました。
YouTubeのvideoIDが不正ですインスタの発信も細々とやっています。
興味がある方は、ぜひリンクをクリックして確認してみてください!
おわり
Discussion