経緯
実務でJavaScriptの修正をしたら『非同期処理と同期処理がごちゃごちゃしそうで怖い』と指摘を貰った。しかし非同期処理と同期処理の違いについて分からなかったため、その内容について調べたことを記しておく。
結論から言うと、「非同期処理と同期処理がごちゃごちゃしそうで怖い」という指摘は、コードの実行の流れ(タイミング)が混乱しそうだ、つまり、どの処理がいつ実行されるのかが分かりにくいという懸念を表していたらしい。
いつ実行されるのかが分かりにくければ、どのタイミングでデータ取得されるのかが分かかりにくいということなので、後々にプログラミングするとき色々と大変そうなのは容易に想像できる。
同期処理
- 上から順に1行ずつ実行される。
- 前の処理が終わるまで次に進まない。
console.log("A");
console.log("B");
console.log("C");
// 出力順: A → B → C
非同期処理
- 処理が「終わるのを待たずに」次に進む
- 結果はあとで返ってくる。
console.log("A");
setTimeout(() => console.log("B"), 1000);
console.log("C");
// 出力順: A → C → B(Bは1秒後)
具体的に起きる問題
非同期処理と同期処理がごちゃごちゃしているときに起きる問題として、データが届いていないのに次の処理を進めてしまう、というバグがある。例としては以下の通り。
let userData;
// APIからデータを取得(非同期処理)
fetch("https://api.example.com/user")
.then(response => response.json())
.then(data => {
userData = data;
});
// データの取得完了を待たずに実行される(同期処理)
console.log(userData.name); // TypeError: Cannot read properties of undefined
この問題を防ぐために、非同期処理を同期処理のように扱う必要がある。それを実現する方法として、主に async / await を使用する。
// 非同期処理を行う関数には async を付ける
async function getUserData() {
try {
// await を付けることで、fetch の完了(レスポンス到着)を待つ
const response = await fetch("https://api.example.com/user");
const userData = await response.json();
// データが確実に取得できた後に実行される
console.log(userData.name);
} catch (error) {
console.error("取得失敗:", error);
}
}
getUserData();
await を使うことで、コードの見た目は上から順に実行される同期処理と同じようになり「データが無い状態での実行」を防ぐことができる。もちろんasync / awaitで囲われていない下の部分は、上から順に実行されていく。イメージとしては以下の通り。
// 非同期関数
async function fetchUser() {
console.log("2. データ取得を開始します");
// ここで一時中断! (データが届くまでこの関数内だけストップ)
const response = await fetch("https://api.example.com/user");
const data = await response.json();
console.log("4. データ取得が完了しました:", data.name);
}
// 実行してみる
console.log("1. 処理を開始します");
fetchUser(); // 関数を呼び出す
console.log("3. 画面の更新や別の処理を実行します");
出力結果
- 処理を開始します
- データ取得を開始します
- 画面の更新や別の処理を実行します
- データ取得が完了しました