はじめに
JavaScriptの非同期処理メモ
参考
まとめ
- 前提としてJavaScriptはシングルスレッドのため非同期処理の終了まで停止して待たないノンブロッキング(待たない)である。
- だが、非同期処理を複数考える場合、終了の順番によって、結果が常にランダムになる(終わる順番が保証されていない) -> 非同期処理を順番に実行したい
- 非同期処理を順番に実行
-
コールバックで実現
->Promiseのthenで実現
->async/awaitで実現
-
- TypeScriptで非同期処理を扱う場合は
async/await
を利用する -
async/await
はPromise
のシンタックスシュガーなため、Promise
は理解する
非同期処理とは
- 多くの場合時間がかかる処理で、裏で行われるもの
- 通信処理とかディスクアクセス処理とか
JavaScriptの非同期処理
JavaScriptの実行環境
が、非同期処理についてはバックグラウンドで処理させて、次の処理に移れるような仕組みを持っている。
例えば、setTimeout関数は非同期処理なので、setTimeoutの処理終了をまたずに次の処理が実行される、など
JavaScript自体はシングルスレッドで動作する言語ですが、それを実行する環境(例: ブラウザやNode.js)は非同期処理をバックグラウンドでサポートする仕組みを提供しているはず
非同期処理に順序を付けたい場合
非同期処理関数のコールバック関数
- 非同期処理の完了をコールバック関数で検知する仕組み
- 非同期処理を順番に実行する場合、非同期処理にコールバック関数を渡していた。そして、コールバック関数の中に次の非同期処理とそのコールバックをネストする形で記述して順番通りに非同期処理を実行していた。コールバック関数を使わないと、非同期処理が終わった順番で実行されてしまう。
- このコールバック関数の利用方法(コールバック関数の引数の数、型など)が各APIで違っていて、毎回調べる必要があった
しかし、非同期処理を重ねていくとネストがどんどん深くなってコードが見づらくなり、流れも追いにくくなります。これがコールバック地獄
です。
Promise
- Promiseを使った非同期処理の関数は、コールバック関数を引数に取るのではなくPromiseオブジェクトを返します
- コールバック関数を使う場合は、API毎にコールバック関数をどのように渡すのか毎回調べる必要があったが、Promiseの使い方さえ覚えれば非同期処理を受け取れるようになった
非同期処理を扱う使い方がそもそも便利になった↑ - もちろん、thenをつなげることで簡潔に非同期処理を順番に連鎖させることができるようになった
Promiseをイメージする
Promiseは非同期処理そのものを表している
const p = new Promise<number>((resolve) => {
// setTimeoutは非同期処理関数
// ライブラリもここに非同期処理が記述されていて、その中でresolve()を利用しているはず
setTimeout(() => {
resolve(100);
}, 3000);
});
p.then((num) => {
console.log(num);
});
- resolveは内部的に用意される関数で、非同期処理が終わったらresolveを呼び出されることを求められている。なのでここでは3秒後にresolve()している
- resolve()が呼び出されると、Promiseが成功に解決される
- 引数の関数は(resolve) =>{...} 即座に実行される
- イメージ
- 例えばfetchはPromiseを返すが、非同期処理が引数のURLの通信で、それは即座に実行されていて、通信が終わればresolve()を実行していて、Promiseのthenが呼べる状態になっているはず、、
さらに便利になったasync/await
-
非同期処理をPromiseを意識しないで記述できるようになった。なので非同期処理を書く場合は大抵の場合
async/await
を記述すると思われる -
async/await
を使うと、より同期的な処理の書き方で非同期処理を順番に記述できるようになった -
エラー処理もtyr-catch処理で記述することができる
-
asyncもawaitも
Promise
を返すので、Promise
を理解していることが大事 -
awaitを使うと、asyncの関数が中断される、asyncの関数の次から実行される
function exampleFunction = async() => {
// asyncの中でawaitを書くことで同期的に処理をかけるようになる
console.log("Start of async function");
const result = await new Promise((resolve) => {
setTimeout(() => {
resolve("Resolved after 2 seconds");
}, 2000);
});
console.log(result);
console.log("End of async function");
}
console.log("Before calling async function");
exampleFunction();
console.log("After calling async function");
表示
Before calling async function
Start of async function
After calling async function
Resolved after 2 seconds
End of async function