TechBlog

非同期処理入門|Promise・async/awaitの仕組みと使い方をわかりやすく解説

by あくえり
#JavaScript #非同期処理 #Promise #async/await #初心者
JavaScript非同期処理入門
目次

同期処理と非同期処理の違い

JavaScriptを学んでいると「非同期処理」という言葉に壁を感じる方が多いと思います。まずは直感的な例えで概念を掴みましょう。

レストランで注文するときを想像してください。

  • 同期処理(悪い例): お客さん全員がカウンターに並び、一人ずつ注文して料理が出てくるのを待ってから次の人が注文する。お客さんが10人いたら、最後の人は9人分の待ち時間が発生します。
  • 非同期処理(実際のレストラン): 各テーブルで注文を受け付け、料理が完成したら持っていく。厨房が料理を作っている間も他のテーブルの注文を受け付けられます。

JavaScriptでは、サーバーからデータを取得するような「時間のかかる処理」を非同期で行うことで、その間もブラウザが止まらずに動き続けられます。


コールバック関数:最初の非同期パターン

非同期処理の最も古い方法がコールバック関数です。「処理が終わったら呼び出してほしい関数」を渡す方法です。

// setTimeout は指定ミリ秒後にコールバックを実行する
console.log('開始');

setTimeout(() => {
  console.log('1秒後に実行される処理');
}, 1000);

console.log('終了'); // 「1秒後に実行される処理」より先に表示される

出力の順番:

開始
終了
1秒後に実行される処理

コールバック地獄の問題

複数の非同期処理を順番に実行しようとすると、ネストが深くなり読みにくくなります。

// コールバック地獄の例(読みにくい!)
fetchUser(userId, (user) => {
  fetchPosts(user.id, (posts) => {
    fetchComments(posts[0].id, (comments) => {
      // さらに深くなる...
      console.log(comments);
    });
  });
});

この問題を解決するためにPromiseが登場しました。


Promise:非同期処理を扱いやすくする仕組み

Promiseは「いずれ値が得られる(または失敗する)」という約束を表すオブジェクトです。

// Promiseの基本的な作り方
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  if (success) {
    resolve('成功しました!');   // 成功時
  } else {
    reject(new Error('失敗しました'));  // 失敗時
  }
});

// .then() で成功時、.catch() で失敗時の処理を書く
myPromise
  .then((result) => {
    console.log(result); // '成功しました!'
  })
  .catch((error) => {
    console.error(error.message);
  });

.then() でチェーン

Promiseを使うとコールバック地獄を解消できます。

fetchUser(userId)
  .then((user) => fetchPosts(user.id))
  .then((posts) => fetchComments(posts[0].id))
  .then((comments) => {
    console.log(comments);
  })
  .catch((error) => {
    console.error('どこかでエラーが発生しました:', error);
  });

フラットで読みやすくなりました。しかしさらに読みやすくしたのがasync/awaitです。


async/await:最もモダンな書き方

async/await はPromiseをベースにした構文糖です。非同期処理を同期処理のように書けるため、コードが直感的になります。

// async を関数の前につけると、その関数はPromiseを返す
async function fetchUserData(userId) {
  // await をつけると Promise が解決されるまで待つ
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const user = await response.json();
  return user;
}

fetchUserData(1).then((user) => console.log(user));

エラーハンドリングは try/catch で

async function fetchUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);

    if (!response.ok) {
      throw new Error(`HTTPエラー: ${response.status}`);
    }

    const user = await response.json();
    return user;
  } catch (error) {
    console.error('データの取得に失敗しました:', error.message);
    return null;
  }
}

fetch API での実践例

実際のWebアプリで使う fetchasync/await の組み合わせを見てみましょう。無料で使えるテスト用API「JSONPlaceholder」を使います。

GETリクエスト(データ取得)

async function getPosts() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts');

    if (!response.ok) {
      throw new Error(`取得に失敗しました: ${response.status}`);
    }

    const posts = await response.json();
    console.log(`${posts.length}件の投稿を取得しました`);
    console.log(posts[0]);
    // { userId: 1, id: 1, title: '...', body: '...' }
  } catch (error) {
    console.error(error.message);
  }
}

getPosts();

POSTリクエスト(データ送信)

async function createPost(title, body) {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ title, body, userId: 1 }),
    });

    if (!response.ok) {
      throw new Error(`送信に失敗しました: ${response.status}`);
    }

    const newPost = await response.json();
    console.log('作成されました:', newPost);
    // { id: 101, title: '...', body: '...', userId: 1 }
  } catch (error) {
    console.error(error.message);
  }
}

createPost('テスト投稿', 'これはテスト本文です');

複数の非同期処理を並列実行する

順番に実行する必要がない複数の非同期処理は、Promise.all を使って並列実行することで効率化できます。

async function fetchMultipleData() {
  // 3つのリクエストを同時に実行
  const [users, posts, todos] = await Promise.all([
    fetch('https://jsonplaceholder.typicode.com/users').then(r => r.json()),
    fetch('https://jsonplaceholder.typicode.com/posts').then(r => r.json()),
    fetch('https://jsonplaceholder.typicode.com/todos').then(r => r.json()),
  ]);

  console.log(`ユーザー: ${users.length}件`);
  console.log(`投稿: ${posts.length}件`);
  console.log(`Todo: ${todos.length}件`);
}

fetchMultipleData();

順番に実行すると合計3回分の待ち時間が発生しますが、Promise.all を使うと最も遅いリクエストが終わる時間だけで済みます。


学習リソース

非同期処理を含めJavaScriptを体系的に学ぶには以下の書籍がおすすめです。

JavaScript本格入門(改訂3版)

JavaScriptの基礎から非同期処理、ES2022の最新構文まで網羅した定番入門書。Promise・async/awaitの章も充実しています。

※ アフィリエイトリンクを含みます

Udemy — 【JS】ガチで学びたい人のためのJavaScriptメカニズム

JavaScriptの内部動作(イベントループ、スコープ、クロージャ)から非同期処理まで深く学べる動画講座。図解でわかりやすいと評判です。

¥1,500〜 Udemyで見る

※ アフィリエイトリンクを含みます


まとめ

方式特徴
コールバック古い書き方。ネストが深くなる(コールバック地獄)
Promise.then() / .catch() でチェーン。読みやすい
async/await最もモダン。同期処理のように書けて直感的

現在のJavaScript開発では async/await が標準です。fetch を使ったAPIとの通信は実務で必ず使うので、ブラウザの開発者ツールのコンソールで実際に試しながら感覚を掴んでください。

共有: