この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:3
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: 非同期処理
SEOタイトル: JavaScript 非同期処理完全ガイド (Promise/async/await)

この記事の要点
  • JavaScript の非同期は Callback → Promise → async/await の順に進化
  • Promise: new Promise((resolve, reject) => ...)then/catch/finally で結果を扱う
  • async/await: Promise を同期コードのように書ける糖衣構文。try/catch で例外捕捉
  • 並列実行: Promise.all (全成功) / allSettled (全完了) / race (最初) / any (最初の成功)
  • Event Loop: Microtask (Promise) は Macrotask (setTimeout) より優先される

非同期処理の進化

// 1. Callback (古い)
fs.readFile('a.txt', (err, dataA) => {
  if (err) return console.error(err);
  fs.readFile('b.txt', (err, dataB) => {
    if (err) return console.error(err);
    fs.readFile('c.txt', (err, dataC) => {
      // Callback Hell
    });
  });
});

// 2. Promise (ES6)
fs.promises.readFile('a.txt')
  .then(a => fs.promises.readFile('b.txt'))
  .then(b => fs.promises.readFile('c.txt'))
  .then(c => console.log('done'))
  .catch(err => console.error(err));

// 3. async / await (ES2017)
async function load() {
  try {
    const a = await fs.promises.readFile('a.txt');
    const b = await fs.promises.readFile('b.txt');
    const c = await fs.promises.readFile('c.txt');
  } catch (err) {
    console.error(err);
  }
}

Promise の基本

// 自分で Promise を作る
const sleep = (ms) => new Promise((resolve, reject) => {
  setTimeout(resolve, ms);
});

await sleep(1000);   // 1秒待つ

// 失敗するパターン
const fetchUser = (id) => new Promise((resolve, reject) => {
  if (id < 0) return reject(new Error('invalid id'));
  setTimeout(() => resolve({ id, name: 'taro' }), 100);
});

// then / catch / finally
fetchUser(1)
  .then(user => console.log(user))
  .catch(err  => console.error(err))
  .finally(() => console.log('done'));

// Promise の 3 状態
// pending   → 進行中
// fulfilled → 成功
// rejected  → 失敗

async / await

// async 関数は必ず Promise を返す
async function getUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

// 呼び出し側も await が必要
async function main() {
  try {
    const user = await getUser(1);
    console.log(user);
  } catch (err) {
    console.error(err);
  }
}

// Top-level await (ES2022, ESM のみ)
// const user = await getUser(1);   // ESM ファイル直下で OK

// async が返すのは常に Promise
const r = main();
console.log(r instanceof Promise);   // true

並列実行 (Promise.all / allSettled / race / any)

// 全部成功なら resolve。1 つでも失敗で reject
const [a, b, c] = await Promise.all([
  fetch('/api/a'),
  fetch('/api/b'),
  fetch('/api/c'),
]);

// 全部完了 (成否問わず) → 各結果を {status, value/reason} で返す
const results = await Promise.allSettled([
  fetch('/api/a'),
  fetch('/api/b'),   // 失敗してもOK
]);
for (const r of results) {
  if (r.status === 'fulfilled') console.log(r.value);
  else                          console.error(r.reason);
}

// 最初に解決 (成功 / 失敗どちらでも) したものを返す
const fastest = await Promise.race([
  fetch('/api/server1'),
  fetch('/api/server2'),
  new Promise((_, rej) => setTimeout(rej, 3000)),   // タイムアウト
]);

// 最初に成功したものを返す (ES2021)
const ok = await Promise.any([
  fetch('/api/a').then(r => r.ok ? r : Promise.reject()),
  fetch('/api/b').then(r => r.ok ? r : Promise.reject()),
]);

逐次 vs 並列 (パフォーマンス)

// ❌ 逐次 (3 秒かかる)
async function loadSequential() {
  const a = await fetch('/a');   // 1s
  const b = await fetch('/b');   // 1s
  const c = await fetch('/c');   // 1s
  return [a, b, c];
}

// ✅ 並列 (1 秒で済む)
async function loadParallel() {
  const [a, b, c] = await Promise.all([
    fetch('/a'),
    fetch('/b'),
    fetch('/c'),
  ]);
  return [a, b, c];
}

// 依存があるなら逐次でOK
async function depend() {
  const user = await fetch(`/me`).then(r => r.json());
  const posts = await fetch(`/users/${user.id}/posts`);
  return posts;
}

Event Loop と Microtask

console.log('1');

setTimeout(() => console.log('2'), 0);   // Macrotask

Promise.resolve().then(() => console.log('3'));   // Microtask

console.log('4');

// 出力順:
// 1
// 4
// 3   ← Promise (Microtask) は同期処理直後に走る
// 2   ← setTimeout (Macrotask) はその後

// → Microtask Queue は Macrotask 1 回ごとに「全部」消化される
//   Promise.resolve().then(x).then(y).then(z) は全部 2 より先に

エラーハンドリングのベストプラクティス

// ❌ async 関数で reject を放置 → unhandled rejection
async function bad() {
  fetch('/api');   // await 無し → エラー時に握り潰し
}

// ✅ await + try/catch
async function good() {
  try {
    const res = await fetch('/api');
    if (!res.ok) throw new Error(res.statusText);
  } catch (err) {
    console.error(err);
  }
}

// ✅ Promise なら .catch を必ず付ける
fetch('/api')
  .then(r => r.json())
  .catch(err => console.error(err));   // ← 必ず

// 一括 unhandled rejection キャッチ (グローバル)
window.addEventListener('unhandledrejection', (event) => {
  console.error('unhandled:', event.reason);
  event.preventDefault();
});
// Node.js
process.on('unhandledRejection', (err) => { /* ... */ });

setTimeout の最小遅延

// 「0 ミリ秒」でも最低でも 4ms 遅延がブラウザ仕様
setTimeout(() => console.log('!'), 0);
// → 実際には 4ms 以上の遅延

// 即座にやりたいなら queueMicrotask
queueMicrotask(() => console.log('next tick'));

// または Promise.resolve().then
Promise.resolve().then(() => console.log('next tick'));

// Node.js
setImmediate(() => {});   // Macrotask 次
process.nextTick(() => {}); // Microtask より先

Worker / Atomics (真の並列)

// Web Worker (UI スレッドと別の実行コンテキスト)
const w = new Worker('worker.js');
w.postMessage({ task: 'heavy' });
w.onmessage = (e) => console.log(e.data);

// worker.js
self.onmessage = (e) => {
  const result = heavyCompute(e.data);
  self.postMessage(result);
};

// SharedArrayBuffer + Atomics で複数 Worker が共有メモリ操作
const buf = new SharedArrayBuffer(1024);
const view = new Int32Array(buf);
Atomics.add(view, 0, 1);
Atomics.wait(view, 0, 0);
Atomics.notify(view, 0, 1);

Node.js: util.promisify

// 古い callback API を Promise 化
const { promisify } = require('util');
const fs = require('fs');

const readFileAsync = promisify(fs.readFile);
const data = await readFileAsync('a.txt', 'utf8');

// Node 10+ なら fs.promises 標準
const fs2 = require('fs').promises;
const data2 = await fs2.readFile('a.txt', 'utf8');

FAQ

Q: thenawait どちらを使うべき?
A: 基本は async/await (読みやすい)。then はチェーン用、特に Promise を流す中で副作用を挟むときに便利。

Q: for (const x of array) await fn(x) は遅い?
A: 直列実行になるので遅い。並列なら Promise.all(array.map(fn))。順序保証 + 並列度制限したいなら p-limit ライブラリ。

Q: Promise を return しないとエラーは?
A: async 関数内で return await fn()return fn() はどちらも OK。ただし try/catch 内では return await しないと例外捕捉できないので注意。