⏱️

JavaScriptで始める非同期処理入門

に公開

JavaScriptで始める非同期処理入門

JavaScriptでウェブアプリケーションを開発していると、必ず出会うのが非同期処理です。APIからデータを取得したり、ファイルを読み込んだり、タイマー処理を実装したりと、様々な場面で非同期処理が必要になります。

この記事では、JavaScriptにおける非同期処理の基本を理解し、実践的なコードで学んでいきましょう。

なぜ非同期処理が必要か?

JavaScriptは基本的にシングルスレッドで動作します。これは一度に1つの処理しか実行できないことを意味します。

例えば、サーバーからデータを取得する処理が3秒かかるとしましょう。この間、同期的に処理を行うと、ユーザーインターフェースが完全に固まってしまいます。ボタンをクリックしても、スクロールしても反応しない状態になってしまいます。

非同期処理を利用することで、データ取得中もUIの操作を可能にし、ユーザー体験を向上させることができます。

非同期処理の基本:コールバック関数

非同期処理の最も古典的な方法はコールバック関数です。処理が完了したら実行する関数を引数として渡します。

// setTimeout を使った例
console.log("処理開始");

setTimeout(() => {
  console.log("3秒後に実行されました");
}, 3000);

console.log("処理は続行しています");

実行結果:

処理開始
処理は続行しています
3秒後に実行されました

しかし、コールバック関数を多用すると「コールバック地獄」と呼ばれる可読性の低いコードになりがちです。

getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getEvenEvenMoreData(c, function(d) {
        getFinalData(d, function(final) {
          console.log(final);
        });
      });
    });
  });
});

モダンな非同期処理:Promise

ES6から導入されたPromiseは、非同期処理をより扱いやすくするための仕組みです。

// Promiseの基本的な使い方
const myPromise = new Promise((resolve, reject) => {
  // 非同期処理を実行
  const success = true;
  
  if (success) {
    resolve("成功しました!");  // 処理成功
  } else {
    reject("エラーが発生しました");  // 処理失敗
  }
});

// Promiseを使用する
myPromise
  .then(result => {
    console.log(result);  // "成功しました!"
  })
  .catch(error => {
    console.error(error);
  });

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

getData()
  .then(a => getMoreData(a))
  .then(b => getEvenMoreData(b))
  .then(c => getEvenEvenMoreData(c))
  .then(d => getFinalData(d))
  .then(final => {
    console.log(final);
  })
  .catch(error => {
    console.error("エラーが発生しました:", error);
  });

さらに進化した非同期処理:async/await

ES2017で導入されたasync/awaitは、Promiseをさらに扱いやすくする構文です。非同期処理を同期処理のように書けるため、コードの可読性が大幅に向上します。

async function fetchData() {
  try {
    const a = await getData();
    const b = await getMoreData(a);
    const c = await getEvenMoreData(b);
    const d = await getEvenEvenMoreData(c);
    const final = await getFinalData(d);
    
    console.log(final);
    return final;
  } catch (error) {
    console.error("エラーが発生しました:", error);
  }
}

// 関数を実行
fetchData();

実践的な例:Fetch APIでデータを取得する

最後に、実際のウェブ開発でよく使うFetch APIを使った非同期データ取得の例を見てみましょう。

// Promise を使った例
function fetchUserData(userId) {
  return fetch(`https://api.example.com/users/${userId}`)
    .then(response => {
      if (!response.ok) {
        throw new Error('ネットワークエラーが発生しました');
      }
      return response.json();
    });
}

fetchUserData(123)
  .then(userData => {
    console.log(userData);
  })
  .catch(error => {
    console.error('データの取得に失敗しました:', error);
  });

// async/await を使った例
async function fetchUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    
    if (!response.ok) {
      throw new Error('ネットワークエラーが発生しました');
    }
    
    const userData = await response.json();
    return userData;
  } catch (error) {
    console.error('データの取得に失敗しました:', error);
    throw error;
  }
}

// 使用例
async function displayUserInfo() {
  try {
    const user = await fetchUserData(123);
    console.log(user);
  } catch (error) {
    // エラー処理
  }
}

まとめ

JavaScriptの非同期処理は、以下のように進化してきました:

  1. コールバック関数: 古典的だが、ネストが深くなりがち
  2. Promise: 連鎖的な処理が書きやすく、エラーハンドリングも改善
  3. async/await: 同期コードのような書き方で可読性が向上

どの方法も状況によって使い分けることが大切です。特に最近のプロジェクトでは、async/awaitを基本としつつ、Promise.allなどのPromiseメソッドと組み合わせて使うのが一般的です。

非同期処理を理解し、適切に使いこなせるようになれば、より快適なユーザー体験を提供できるウェブアプリケーションが開発できるようになるでしょう。

参考リンク

Discussion