JavaScript 逆引き集 | async/await 基本

JavaScript JavaScript
スポンサーリンク

async/await 基本 — async function f(){ const r = await p }

async/await は「非同期処理を同期処理のように読みやすく書く」ための構文です。Promise を裏で使いつつ、直線的な書き味で成功と失敗を表現できます。


基本の書き方

// async を付けた関数は「必ず Promise を返す」
async function greet() {
  return "hello"; // 実体は Promise.resolve("hello")
}

greet().then(console.log); // "hello"
JavaScript
// await は「Promise が解決されるまで待って結果を受け取る」
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function run() {
  await delay(300);      // 300ms待つ
  const value = 42;      // 待った後の処理
  console.log(value);    // 42
}

run();
JavaScript
  • async: 関数に付けると、その関数は常に Promise を返す。return 値は自動で resolve される。
  • await: Promise の完了(成功/失敗)を“その場で”待つ。成功なら値、失敗なら例外として投げられる。

エラーハンドリング(try/catch/finally)

async function fetchUser(id) {
  // 疑似API
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      id > 0 ? resolve({ id, name: "Aki" }) : reject(new Error("Invalid id"));
    }, 200);
  });
}

async function main() {
  try {
    const user = await fetchUser(1);
    console.log("OK:", user);
  } catch (e) {
    console.error("NG:", e.message);
  } finally {
    console.log("終わり");
  }
}

main();
JavaScript
  • ポイント: await が投げる例外を try/catch で受ける。後処理は finally に集約。

逐次実行と並列実行の使い分け

逐次(順番に待つ)

async function sequential() {
  const a = await task(300); // 300ms
  const b = await task(200); // 次に200ms
  return [a, b];             // 合計約500ms
}
JavaScript

並列(同時に走らせて、まとめて待つ)

async function parallel() {
  const pa = task(300);      // すぐ開始
  const pb = task(200);      // すぐ開始
  const [a, b] = await Promise.all([pa, pb]); // 約300ms
  return [a, b];
}

function task(ms) {
  return new Promise(res => setTimeout(() => res(ms), ms));
}
JavaScript
  • 直感: 順番依存なら逐次、依存がないなら Promise.all で並列に。

よく使うテンプレート集

ラップ関数(コールバック→Promise に包む)

function delay(ms) {
  return new Promise(res => setTimeout(res, ms));
}
JavaScript

タイムアウトを付ける

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

async function loadWithTimeout() {
  const p = new Promise(res => setTimeout(() => res("done"), 500));
  return await withTimeout(p, 300); // 300msでタイムアウト
}
JavaScript

リトライ(固定回数)

async function retry(fn, times = 3) {
  let lastErr;
  for (let i = 0; i < times; i++) {
    try { return await fn(); }
    catch (e) { lastErr = e; }
  }
  throw lastErr;
}

// 使用例
retry(() => fetchUser(1)).then(console.log).catch(console.error);
JavaScript

順番保証つきキュー(逐次で処理)

class Queue {
  #current = Promise.resolve();
  enqueue(task) {
    const run = async () => task();     // task は async 関数でもOK
    this.#current = this.#current.then(run, run);
    return this.#current;
  }
}
JavaScript

よくある落とし穴と対策

  • await を忘れる: Promise を返すだけで中身が実行されず、意図した順序にならない。
    • 対策: その場で結果が必要なら必ず await、並列したいなら意図的に await を遅らせる。
  • new Promise の乱用: すでに Promise を返す API をさらに new Promise で包むのはアンチパターン。
    • 対策: 既存の Promise をそのまま await。
  • try/catch の範囲が狭い/広すぎる: 失敗箇所が分かりづらくなる。
    • 対策: 失敗しうる await の粒度で囲む。後処理は finally。
  • 逐次にしてしまう: 依存関係がないのに連続 await で遅くなる。
    • 対策: 先に Promise を作って Promise.all
  • トップレベル await の注意: モジュール(ESM)でのみ使用可。複数 await で初期化を遅らせると起動時間に影響。
    • 対策: 初期化は並列化、必要最小限の待機に絞る。

練習問題(手を動かして覚える)

// 1) 基本: awaitで値を受け取る
async function ex1() {
  const r = await Promise.resolve("OK");
  console.log(r); // "OK"
}
ex1();

// 2) try/catch: 例外を捕まえる
async function ex2() {
  try {
    await Promise.reject(new Error("fail"));
  } catch (e) {
    console.log("caught:", e.message); // "fail"
  }
}
ex2();

// 3) 逐次 vs 並列
const t = ms => new Promise(res => setTimeout(() => res(ms), ms));
async function seq() {
  const a = await t(200);
  const b = await t(300);
  console.log("seq:", a + b); // 約500ms
}
async function par() {
  const [a, b] = await Promise.all([t(200), t(300)]);
  console.log("par:", a + b); // 約300ms
}
seq(); par();

// 4) タイムアウト付きで取得
async function ex4() {
  const slow = new Promise(res => setTimeout(() => res("slow"), 400));
  try {
    const r = await withTimeout(slow, 200);
    console.log(r);
  } catch (e) {
    console.log("timeout:", e.message);
  }
}
ex4();
JavaScript

直感的な指針

  • 成功は値、失敗は例外で受ける。
  • 依存がなければ並列化して Promise.all。
  • await の位置で順序が決まる。意図して置く。
  • try/catch/finally で失敗と後始末を明確に。
  • 既存 Promise はそのまま await、余計なラップをしない。
タイトルとURLをコピーしました