概要
JavaScriptの非同期処理は、単なる記法の問題ではない。
それは制御フローと責務の設計に深く関わる領域だ。
非同期処理がスパゲッティ化するのは、「Promiseは使ってるけど、なぜそう書くべきかを理解していない」から。
この記事では、非同期処理の成り立ちと、Promise
/ async-await
/ then
をどのように使い分け、保守性と可読性に優れた非同期ロジックを組むかを実践ベースで解説する。
対象環境
ES6(Promise)以降のJavaScript環境
Node.js / ブラウザ両対応
非同期処理の背景:なぜ必要なのか?
JavaScriptはシングルスレッドの言語。
UIスレッドをブロックせずにI/O処理やタイマー、ネットワーク通信を扱うために、非同期処理が必要となる。
過去 → 現在への進化
callback hell
↓
Promise(ES6)
↓
async/await(ES2017)
Promiseの本質
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('データ取得成功');
}, 1000);
});
};
- Promise は「未来の値」を扱う箱
- 状態:
pending
→fulfilled
またはrejected
- メソッド:
.then()
/.catch()
/.finally()
async / await の意味と強み
async function load() {
try {
const result = await fetchData();
console.log(result);
} catch (error) {
console.error('失敗:', error);
}
}
-
await
で Promise の解決を待つ(同期的に見える非同期) -
try / catch
でエラー制御ができる - 読みやすく、制御構造が明確
thenチェーンとの比較:どう違うのか?
then / catch(従来型)
fetchData()
.then((res) => console.log(res))
.catch((err) => console.error(err));
→ 短くて良いが、複雑なロジックではネストが深くなる
実務での使い分け指針
ケース | 適した形式 | 理由 |
---|---|---|
短い一連の処理 | .then() |
その場で完結できる。軽量。 |
複雑なフロー / 分岐あり | async / await |
処理の見通しが立ちやすい。 |
ライブラリ間の橋渡し |
Promise 明示定義 |
汎用性が高く、チェーン可能。 |
外部APIラッパー実装 | Promise | 状態管理と拡張性に優れる。 |
エラーハンドリング戦略
✅ 基本的な書き方(async/await)
try {
const res = await fetch(url);
if (!res.ok) throw new Error('HTTPエラー');
const data = await res.json();
} catch (err) {
console.error(err);
}
✅ 最終保障 .catch()
or .finally()
fetch(url)
.then(process)
.catch(logError)
.finally(() => console.log('完了'));
→ リソース解放・ローダー終了処理などには .finally()
が有効
よくあるミスと対策
❌ await をトップレベルで使う(ES2017以前)
const data = await fetch(); // SyntaxError
→ ✅ モジュールスコープでは async function
でラップするか、top-level await
(ES2022〜)を使う
❌ Promise を忘れて普通の関数にしてしまう
function fetchData() {
setTimeout(() => {
return 'データ'; // ← これは返らない
}, 1000);
}
→ ✅ return new Promise(...)
で包むこと
設計視点:非同期処理をどう“責務”として切り分けるか
- データ取得関数は非同期性を隠蔽しない(呼び出し側が制御できるように)
- UI操作は同期、状態取得・保存は非同期と明示的に分ける
- 状態管理(例:Vuex / Redux)と非同期処理の橋渡しは中間層で制御
結語
非同期処理は、JavaScriptの“非同期性”ではなく、構造の問題だ。
どこで待ち、どこで処理し、どこで責任を持つか。
それを意図して設計できてこそ、非同期は制御できる。
動けばいいではなく、読みやすく、拡張可能で、安全な非同期処理を設計する。
その第一歩は、Promise / async-await の違いを「使える」ではなく「選べる」ようになることだ。