JavaScriptでは、時間がかかる処理(API通信・ファイル読み込み・タイマーなど)を実行している間も、他のコードを止めずに処理を進める非同期処理が重要な役割を果たします。
本記事では、JavaScriptの非同期処理の基本と、Promise
、async/await
の使い方を解説します。
目次
非同期処理とは?
非同期処理(Asynchronous Processing)とは、ある処理が完了するのを待たずに、次の命令を先に実行していく処理方法のことです。
例えば:
console.log("処理開始");
setTimeout(() => {
console.log("2秒後の処理");
}, 2000);
console.log("処理終了");
出力結果:
処理開始
処理終了
2秒後の処理
このように、setTimeout
による処理は後回しにされ、次のコードが先に実行されます。
コールバック関数とその課題
非同期処理は、もともとコールバック関数で対応していました。
function getData(callback) {
setTimeout(() => {
callback("データを取得しました");
}, 1000);
}
getData((data) => {
console.log(data);
});
しかし、コールバックがネストしていくと、いわゆる「コールバック地獄」になり、コードが読みにくくなります。
Promiseとは?
Promise
は、非同期処理の結果を表すオブジェクトで、コールバック地獄を回避するために導入されました。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("データ取得成功");
}, 1000);
});
}
fetchData().then((result) => {
console.log(result);
});
状態(state)
Promiseは3つの状態を持ちます:
pending
(保留中)fulfilled
(成功)rejected
(失敗)
catchでエラーハンドリング
fetchData()
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error("エラー:", error);
});
async/awaitとは?
async
とawait
は、Promiseをより同期処理のように書ける構文です。読みやすさと保守性が大幅に向上します。
async function showData() {
try {
const result = await fetchData();
console.log(result);
} catch (error) {
console.error("エラー:", error);
}
}
showData();
ポイント
async
を付けた関数は、必ずPromise
を返すawait
は、Promiseの結果を待つ(成功なら結果、失敗ならエラーを投げる)
Promiseとasync/awaitの使い分け
特徴 | Promise | async/await |
---|---|---|
読みやすさ | 中程度(then/catchのネストあり) | 高い(直列に書ける) |
エラーハンドリング | .catch() を使用 | try/catch で対応 |
並列処理 | Promise.all などを使用 | await Promise.all() で書ける |
実践例:API通信
async function fetchUser() {
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
console.log(user.name);
}
fetchUser();
非同期処理の注意点
非同期処理は非常に便利ですが、以下の点に注意しないと思わぬバグや挙動に繋がります。
1. 処理の順序が保証されない
非同期処理は処理の完了順に従って実行されるため、コードの記述順とは異なる結果になることがあります。
fetchData1();
fetchData2(); // こちらが先に完了する可能性がある
→ 処理順が重要な場合は await
や Promise.then()
を使って制御しましょう。
2. エラーハンドリングを忘れがち
非同期処理では、エラーが起きても即座にスローされず、見落としやすいです。try/catchやcatchメソッドで必ず対応を。
// NG: エラーが握りつぶされる
const res = await fetch(url);
// OK: try-catchで囲む
try {
const res = await fetch(url);
} catch (e) {
console.error("通信に失敗しました", e);
}
3. awaitはforEachでは使えない
Array.prototype.forEach()
内でawait
を使っても、非同期処理を正しく待ってくれません。代わりにfor...of
を使いましょう。
// ❌ 意図通りに動作しない
array.forEach(async (item) => {
await doSomething(item);
});
// ✅ こっちを使う
for (const item of array) {
await doSomething(item);
}
4. 並列処理と直列処理を正しく使い分ける
すべてをawaitで直列に処理すると、処理速度が落ちることがあります。独立した処理は Promise.all()
で並列に行うと高速です。
// 直列(遅い)
await task1();
await task2();
// 並列(速い)
await Promise.all([task1(), task2()]);
これらの注意点を意識すると、非同期処理のバグやパフォーマンス問題を未然に防ぐことができます。開発では「意図的に待つ」「例外に備える」「順序を把握する」を心がけましょう。
まとめ
- 非同期処理は、重たい処理の間もアプリ全体を止めないために必要
- Promiseで非同期処理を扱うと可読性が向上
- async/awaitはPromiseをさらに読みやすくした書き方
今後は、非同期処理が不可欠な場面(API通信、ファイル操作など)で、async/awaitを中心に使っていくのが主流です。