2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptにおける非同期制御の設計戦略:Promiseとasync/awaitを中心とした構造的アプローチ

Posted at

概要

非同期処理は JavaScript における“言語的必然”であり、同期的に書かれたように動く非同期コードこそが設計の鍵となる。

本稿では以下の5領域を軸に、非同期制御を構造的に整理する:

  • Promiseの構文と連鎖の意味
  • async/awaitによる構造化と制御
  • 並列・直列処理の設計分岐
  • エラーハンドリングの責任設計
  • キャンセル・タイムアウト・安全性の担保

1. Promiseの基本構造と連鎖

fetch('/api/user')
  .then(res => res.json())
  .then(user => updateUI(user))
  .catch(err => handleError(err));
  • .then() は逐次処理の連鎖
  • .catch() は任意のポイントでエラーを拾う
  • 非同期は“値の流れ”として設計する

2. async/awaitによる構造化

async function fetchUser() {
  try {
    const res = await fetch('/api/user');
    const user = await res.json();
    updateUI(user);
  } catch (err) {
    handleError(err);
  }
}
  • ✅ 同期的構文で非同期を制御できる
  • ✅ try/catch による構文的な例外制御が可能
  • ✅ 可読性とトレース性の向上

3. 並列と直列の戦略的選択

直列処理(逐次)

const a = await fetch('/a');
const b = await fetch('/b');

→ 各処理が終わるまで次に進まない


並列処理(同時)

const [a, b] = await Promise.all([
  fetch('/a'),
  fetch('/b')
]);

→ ✅ 並列にリクエスト → 全完了後に受け取る


4. エラーハンドリング戦略

.catch() / try-catch は責任の境界

  • 各関数ごとにtry/catchを書く必要はない
  • 一貫して“呼び出し元で責任を持つ”か、“関数内で握り潰す”かを設計する
async function riskyOperation() {
  const res = await fetch('/x');
  if (!res.ok) throw new Error('Failed');
  return res.json();
}

// 呼び出し元で責任を持つ
try {
  const data = await riskyOperation();
} catch (e) {
  notifyUser(e.message);
}

5. タイムアウトとキャンセル設計

タイムアウト実装

function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
}

→ ✅ 最も早く解決されたPromiseを採用


AbortControllerによるキャンセル設計

const controller = new AbortController();
const signal = controller.signal;

fetch('/api/data', { signal })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.warn('Request cancelled');
    }
  });

controller.abort();

→ ✅ DOM標準のキャンセル設計(Fetch API対応)


設計判断フロー

① 複数非同期を順に実行? → await を逐次で使う

② 複数非同期を同時に実行? → Promise.all()

③ 途中で失敗しても残りを実行したい? → Promise.allSettled()

④ タイムアウトを実装したい? → Promise.race() + setTimeout

⑤ 明示的にキャンセルしたい? → AbortController

⑥ 非同期処理の責任をどこで取る? → 呼び出し元 or 関数内でtry/catch

よくあるミスと対策

await をループ内で逐次処理してしまう

for (const item of items) {
  await process(item); // ❌ 非効率
}

→ ✅ Promise.all(items.map(...)) で並列化できる場合も検討


❌ try/catch を書かずに await だけ使う

→ ✅ 非同期関数はエラーを返す → 必ずtry/catchか.catch()で受ける


❌ thenとawaitの混在

await fetch(...).then(...); // ❌ 混乱の元

→ ✅ async関数内では await に統一する


結語

非同期制御は、JavaScriptにおける“未来をどう待つか”の設計である。

  • Promise“未来の値”という契約
  • async/awaitその契約を“構文化”する手段
  • タイムアウト・キャンセル・並列化は “制御構造の戦略”

時間をどう扱うかは、設計者の意志に委ねられている。
制御できる非同期は、読みやすく壊れにくい未来を保証する。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?