JavaScriptのAsync/Awaitの基本と使い方を徹底解説

JavaScriptは、特にWeb開発において広く使用されているプログラミング言語です。JavaScriptの特徴の一つに非同期処理があり、これによりユーザーインターフェースが応答性を維持しつつ、バックグラウンドで処理を行うことができます。しかし、従来の非同期処理方法であるコールバック関数やPromiseを使うと、コードが複雑になりがちです。そこで登場したのがAsync/Awaitです。Async/Awaitを使うことで、非同期処理を同期処理のように記述でき、コードの可読性と保守性が大幅に向上します。本記事では、JavaScriptの非同期処理の基本から始めて、Async/Awaitの使い方とその利点を詳しく解説します。具体的な例やベストプラクティスを通じて、Async/Awaitの理解を深め、実際の開発に役立てましょう。

目次

非同期処理とは

JavaScriptの非同期処理とは、時間のかかる処理(例:ネットワークリクエストやファイルの読み書きなど)をバックグラウンドで実行し、その間に他のコードをブロックせずに進行させることができる機能です。これにより、ユーザーインターフェースはスムーズに動作し続け、ユーザー体験が向上します。

非同期処理の基本概念

非同期処理の基本概念は、ある操作が完了するまで他の操作を待つ必要がないことです。例えば、ウェブページがサーバーからデータを取得している間も、ユーザーはそのページを操作できます。これにより、アプリケーションはより応答性の高いものになります。

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

非同期処理が必要な理由は以下の通りです。

応答性の向上

ユーザーインターフェースが応答し続けるため、ユーザーは待たされることなく操作を継続できます。

パフォーマンスの向上

バックグラウンドで処理を行うことで、メインスレッドをブロックせず、他のタスクを並行して処理できます。

効率的なリソース利用

非同期処理により、CPUやネットワークなどのリソースを効率的に利用できます。

これらの利点から、非同期処理は現代のWebアプリケーション開発において不可欠な技術となっています。次に、非同期処理を実現するためのPromiseについて解説します。

Promiseの基本

Promiseは、JavaScriptで非同期処理を管理するためのオブジェクトです。非同期処理の結果を表し、その処理が成功(解決)または失敗(拒否)したかを追跡します。Promiseを使用することで、非同期コードをより直感的に記述でき、コールバック関数の入れ子(コールバック地獄)を回避することができます。

Promiseの基本構文

Promiseの基本構文は以下の通りです。

const promise = new Promise((resolve, reject) => {
  // 非同期処理を行う
  if (/* 成功 */) {
    resolve('成功の結果');
  } else {
    reject('エラーメッセージ');
  }
});

Promiseの状態

Promiseは3つの状態を持ちます。

Pending(保留)

初期状態。非同期処理が完了していない状態。

Fulfilled(成功)

非同期処理が成功し、resolve関数が呼ばれた状態。

Rejected(失敗)

非同期処理が失敗し、reject関数が呼ばれた状態。

Promiseの利用方法

Promiseの利用方法を具体的な例で説明します。

function asyncTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('タスク完了');
    }, 1000);
  });
}

asyncTask()
  .then(result => {
    console.log(result); // "タスク完了"
  })
  .catch(error => {
    console.error(error);
  });

この例では、asyncTask関数が1秒後に完了する非同期タスクを実行します。thenメソッドはPromiseが成功したときに呼ばれ、catchメソッドは失敗したときに呼ばれます。

Promiseチェーン

Promiseはチェーンすることができます。これにより、複数の非同期処理を順番に実行できます。

asyncTask()
  .then(result => {
    console.log(result); // "タスク完了"
    return anotherAsyncTask();
  })
  .then(result => {
    console.log(result); // "別のタスク完了"
  })
  .catch(error => {
    console.error(error);
  });

このように、Promiseを使用することで、非同期処理をより直感的に管理できるようになります。次に、Async/Awaitの基本的な使い方とその利点を紹介します。

Async/Awaitの概要

Async/Awaitは、JavaScriptのES2017(ES8)で導入された非同期処理を扱うための構文です。Promiseをベースにしており、非同期処理を同期処理のように書くことができるため、コードの可読性と保守性が向上します。

Async/Awaitの基本的な使い方

Async/Awaitを使用するには、非同期関数をasyncキーワードで定義し、その中でawaitキーワードを使ってPromiseの結果を待ちます。以下は基本的な例です。

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

fetchData().then(data => {
  console.log(data);
}).catch(error => {
  console.error('Error:', error);
});

Async関数の利点

Async関数を使うことで得られる主な利点は以下の通りです。

可読性の向上

非同期処理を同期処理のように書けるため、コードの流れがわかりやすくなります。

エラーハンドリングの簡素化

try...catchブロックを使って、エラー処理を直感的に行えます。

Async関数の定義方法

Async関数はasyncキーワードを使って定義します。関数がPromiseを返すことを自動的に保証します。

async function exampleFunction() {
  return 'Hello, Async!';
}

exampleFunction().then(result => {
  console.log(result); // "Hello, Async!"
});

Awaitの使い方

awaitキーワードは、Promiseの結果が返されるまで待ちます。非同期処理が完了するまでコードの実行を一時停止し、結果が返された後に続けて実行します。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

fetchData();

この例では、awaitを使って非同期操作(fetchresponse.json)の完了を待ち、結果を処理しています。

Async/Awaitを使うことで、非同期処理をより簡潔で直感的に記述できるようになります。次に、Async関数の具体的な定義方法とその特徴について詳しく解説します。

Async関数の定義方法

Async関数は、JavaScriptにおける非同期処理を扱うための便利な方法です。非同期関数は、asyncキーワードを使って定義されます。この関数は常にPromiseを返し、関数内でawaitキーワードを使うことで、Promiseの完了を待つことができます。

Async関数の基本的な定義方法

Async関数は、次のようにasyncキーワードを関数宣言の前に付けて定義します。

async function fetchData() {
  // 非同期処理を行う
  return 'データが取得されました';
}

// 関数を呼び出す
fetchData().then(result => {
  console.log(result); // "データが取得されました"
});

この例では、fetchData関数がasyncキーワードで定義されており、関数はPromiseを返します。

非同期関数の特徴

非同期関数にはいくつかの特徴があります。

Promiseを自動的に返す

Async関数は、明示的にPromiseを返さなくても、常にPromiseを返します。このPromiseは、関数内で返される値を解決します。

エラーハンドリングが簡単

非同期関数内で発生したエラーは、自動的にPromiseの拒否として処理されます。これにより、エラー処理が簡単になります。

Async関数内でのAwaitの使用

Async関数内では、awaitキーワードを使ってPromiseの結果を待つことができます。awaitは、Promiseの完了を待ち、その結果を返します。

async function getUserData() {
  try {
    const response = await fetch('https://api.example.com/user');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('エラーが発生しました:', error);
    throw error;
  }
}

// 関数を呼び出す
getUserData().then(userData => {
  console.log(userData);
}).catch(error => {
  console.error('データの取得に失敗しました:', error);
});

この例では、awaitを使って非同期操作(fetchresponse.json)の完了を待ち、その結果を処理しています。エラーが発生した場合は、catchブロックで処理されます。

Async関数を定義し、awaitを使用することで、非同期処理がより直感的で扱いやすくなります。次に、awaitの具体的な使い方とその効果について詳しく解説します。

Awaitの使い方

Awaitは、JavaScriptのAsync関数内で使用されるキーワードで、Promiseの完了を待つために使います。これにより、非同期処理を同期処理のように記述でき、コードの可読性が向上します。

Awaitの基本的な使い方

Awaitを使用するためには、まず非同期関数を定義する必要があります。以下は基本的な例です。

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

fetchData().then(data => {
  console.log(data);
}).catch(error => {
  console.error('Error:', error);
});

この例では、awaitキーワードを使用して、fetch関数が完了し、レスポンスが返されるまで待ちます。その後、response.jsonメソッドの結果を待ち、最終的にデータを返します。

Awaitの効果

Awaitは、非同期処理を同期処理のように見せる効果があります。これにより、コードの流れが直感的に理解しやすくなります。

コードの可読性の向上

Awaitを使用することで、コールバックやPromiseチェーンを使わずに非同期処理を記述できます。これにより、コードがシンプルで読みやすくなります。

エラーハンドリングの簡素化

Awaitを使った非同期処理では、try...catchブロックを使ってエラーハンドリングが容易に行えます。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

fetchData().then(data => {
  console.log(data);
}).catch(error => {
  console.error('Data fetch failed:', error);
});

この例では、try...catchブロックを使用して、非同期処理中に発生する可能性のあるエラーを処理しています。

Awaitの応用例

Awaitは、複数の非同期操作を順番に実行する際にも役立ちます。以下の例では、複数の非同期関数を順番に実行しています。

async function getData() {
  try {
    const userResponse = await fetch('https://api.example.com/user');
    const user = await userResponse.json();

    const postsResponse = await fetch(`https://api.example.com/posts?userId=${user.id}`);
    const posts = await postsResponse.json();

    return { user, posts };
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

getData().then(result => {
  console.log(result.user);
  console.log(result.posts);
}).catch(error => {
  console.error('Error fetching data:', error);
});

この例では、getData関数内でawaitを使い、ユーザー情報を取得した後、そのユーザーに関連する投稿を取得しています。これにより、非同期操作をシンプルで直感的な方法で記述できます。

Awaitを使用することで、非同期処理の記述が大幅に簡素化され、コードの可読性が向上します。次に、Async/Awaitを使用した非同期処理でのエラーハンドリングの方法について詳しく解説します。

エラーハンドリング

Async/Awaitを使用した非同期処理において、エラーハンドリングは非常に重要です。適切なエラーハンドリングを実装することで、アプリケーションの信頼性とユーザー体験を向上させることができます。ここでは、Async/Awaitを使ったエラーハンドリングの方法について解説します。

Try…Catchブロックを使用する

Async関数内で発生するエラーは、通常の同期コードと同様にtry...catchブロックを使用して処理できます。これにより、エラー処理が直感的で一貫性のあるものになります。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch data failed:', error);
    throw error; // エラーを再スローして呼び出し元で処理できるようにする
  }
}

fetchData().then(data => {
  console.log(data);
}).catch(error => {
  console.error('Error in fetchData:', error);
});

この例では、fetch関数でHTTPエラーが発生した場合や、response.jsonメソッドでエラーが発生した場合に、try...catchブロックでエラーをキャッチして処理しています。

エラーの再スロー

エラーをキャッチしてログに記録するだけでなく、再スローすることも重要です。再スローすることで、呼び出し元の関数でもエラーを処理できます。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch data failed:', error);
    throw error; // エラーを再スローする
  }
}

async function main() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.error('Error in main function:', error);
  }
}

main();

この例では、fetchData関数内でキャッチしたエラーを再スローし、main関数で再度キャッチして処理しています。

複数の非同期操作のエラーハンドリング

複数の非同期操作を順番に実行する場合、各操作ごとにエラーハンドリングを行う必要があります。以下の例では、複数の非同期操作を順番に実行し、それぞれの操作でエラーを処理しています。

async function getUserData(userId) {
  try {
    const userResponse = await fetch(`https://api.example.com/users/${userId}`);
    if (!userResponse.ok) {
      throw new Error(`User fetch error: ${userResponse.statusText}`);
    }
    const user = await userResponse.json();
    return user;
  } catch (error) {
    console.error('Error fetching user data:', error);
    throw error;
  }
}

async function getUserPosts(userId) {
  try {
    const postsResponse = await fetch(`https://api.example.com/posts?userId=${userId}`);
    if (!postsResponse.ok) {
      throw new Error(`Posts fetch error: ${postsResponse.statusText}`);
    }
    const posts = await postsResponse.json();
    return posts;
  } catch (error) {
    console.error('Error fetching user posts:', error);
    throw error;
  }
}

async function main() {
  try {
    const userId = 1;
    const user = await getUserData(userId);
    console.log('User:', user);

    const posts = await getUserPosts(userId);
    console.log('Posts:', posts);
  } catch (error) {
    console.error('Error in main function:', error);
  }
}

main();

この例では、ユーザーデータとユーザーの投稿データをそれぞれ非同期に取得し、それぞれの関数でエラーハンドリングを行っています。

Async/Awaitを使用したエラーハンドリングにより、非同期処理の信頼性を高め、エラーの原因を迅速に特定しやすくなります。次に、Async/Awaitの実用的な例を紹介し、実際の開発にどのように役立つかを解説します。

実用的な例

Async/Awaitは、実際の開発において多くの場面で役立ちます。ここでは、Async/Awaitを使用した実用的な例をいくつか紹介し、その具体的な利点と効果を解説します。

APIからのデータ取得

ウェブアプリケーションでよく行われるタスクの一つに、外部APIからデータを取得することがあります。Async/Awaitを使うと、これを非常にシンプルかつ読みやすいコードで実装できます。

async function fetchWeather(city) {
  const apiKey = 'your_api_key';
  const response = await fetch(`https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}`);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  const weatherData = await response.json();
  return weatherData;
}

fetchWeather('London')
  .then(data => {
    console.log('Weather in London:', data);
  })
  .catch(error => {
    console.error('Error fetching weather data:', error);
  });

この例では、指定された都市の天気情報を取得し、コンソールに表示します。fetch関数を使用してAPIからデータを取得し、その結果をawaitで待ちます。

データベースとのやり取り

サーバーサイドのJavaScript(例:Node.js)では、データベースとのやり取りが重要です。Async/Awaitを使うことで、非同期のデータベース操作をシンプルに記述できます。

const { Client } = require('pg'); // PostgreSQLクライアント

async function getUserById(userId) {
  const client = new Client({
    connectionString: 'your_database_url'
  });

  await client.connect();

  try {
    const res = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
    return res.rows[0];
  } catch (error) {
    console.error('Database query failed:', error);
    throw error;
  } finally {
    await client.end();
  }
}

getUserById(1)
  .then(user => {
    console.log('User:', user);
  })
  .catch(error => {
    console.error('Error fetching user:', error);
  });

この例では、PostgreSQLデータベースから特定のユーザーを取得し、コンソールに表示します。データベースクライアントの接続とクエリ実行を非同期で行い、awaitを使ってそれぞれの操作を待ちます。

ファイル操作

Node.jsを使ったファイル操作も、Async/Awaitで簡潔に実装できます。

const fs = require('fs').promises;

async function readFileContent(filePath) {
  try {
    const data = await fs.readFile(filePath, 'utf8');
    return data;
  } catch (error) {
    console.error('Error reading file:', error);
    throw error;
  }
}

readFileContent('./example.txt')
  .then(content => {
    console.log('File content:', content);
  })
  .catch(error => {
    console.error('Error:', error);
  });

この例では、指定されたファイルの内容を読み取り、コンソールに表示します。fs.promises.readFileを使ってファイルを非同期に読み込み、その結果をawaitで待ちます。

連続した非同期操作

複数の非同期操作を順番に実行する場合も、Async/Awaitを使うと非常にわかりやすくなります。

async function processSequentialTasks() {
  try {
    const result1 = await task1();
    console.log('Task 1 completed:', result1);

    const result2 = await task2(result1);
    console.log('Task 2 completed:', result2);

    const result3 = await task3(result2);
    console.log('Task 3 completed:', result3);

    return result3;
  } catch (error) {
    console.error('Error in processing tasks:', error);
    throw error;
  }
}

processSequentialTasks()
  .then(finalResult => {
    console.log('All tasks completed:', finalResult);
  })
  .catch(error => {
    console.error('Error:', error);
  });

この例では、task1task2task3の3つの非同期タスクを順番に実行し、それぞれの結果を次のタスクに渡しています。各タスクが完了するまでawaitで待ち、エラーが発生した場合はcatchブロックで処理します。

Async/Awaitを使うことで、非同期処理を含む複雑なロジックをシンプルに記述でき、コードの可読性と保守性が大幅に向上します。次に、非同期処理のベストプラクティスについて解説します。

非同期処理のベストプラクティス

Async/Awaitを使った非同期処理を効果的に行うためには、いくつかのベストプラクティスを守ることが重要です。これにより、コードの信頼性、可読性、メンテナンス性が向上し、バグの発生を減らすことができます。ここでは、非同期処理を行う際のベストプラクティスを紹介します。

適切なエラーハンドリング

エラーハンドリングは非同期処理において非常に重要です。エラーが発生した場合に備えて、すべてのAsync関数でtry...catchブロックを使用することを習慣化しましょう。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch data failed:', error);
    throw error;
  }
}

この例では、非同期処理中に発生する可能性のあるエラーを適切にキャッチして処理しています。

非同期処理の並行実行

複数の非同期処理を並行して実行する場合、Promise.allを使用すると効率的です。これにより、すべてのPromiseが解決されるまで待つことができます。

async function fetchAllData() {
  try {
    const [data1, data2, data3] = await Promise.all([
      fetchData1(),
      fetchData2(),
      fetchData3()
    ]);
    return { data1, data2, data3 };
  } catch (error) {
    console.error('Error fetching data:', error);
    throw error;
  }
}

fetchAllData().then(result => {
  console.log('All data fetched:', result);
}).catch(error => {
  console.error('Error:', error);
});

この例では、3つの非同期操作を並行して実行し、すべての操作が完了するのを待ってから結果を処理します。

不要なAwaitの使用を避ける

awaitを使うと、Promiseの完了を待つためにコードの実行が一時停止しますが、不要な場所で使用するとパフォーマンスが低下する可能性があります。不要なawaitの使用を避けましょう。

// 悪い例
async function processTask() {
  const result1 = await task1();
  const result2 = await task2(result1);
  return result2;
}

// 良い例
async function processTask() {
  const result1 = task1(); // awaitを省略
  const result2 = await task2(await result1); // 必要な箇所だけawait
  return result2;
}

Promiseチェーンの使用を避ける

Async/Awaitを使用するときは、Promiseチェーンの代わりにawaitを使ってコードの可読性を向上させましょう。

// 悪い例
fetchData()
  .then(data => {
    return processData(data);
  })
  .then(result => {
    console.log('Processed data:', result);
  })
  .catch(error => {
    console.error('Error:', error);
  });

// 良い例
async function handleData() {
  try {
    const data = await fetchData();
    const result = await processData(data);
    console.log('Processed data:', result);
  } catch (error) {
    console.error('Error:', error);
  }
}

handleData();

この例では、Promiseチェーンを使わずにawaitを使うことで、コードの流れが明確になっています。

リソースのクリーンアップ

非同期処理が完了した後にリソースを解放することも重要です。特に、データベース接続やファイルハンドルなどのリソースを適切にクリーンアップしましょう。

const { Client } = require('pg');

async function fetchData() {
  const client = new Client({
    connectionString: 'your_database_url'
  });

  await client.connect();

  try {
    const res = await client.query('SELECT * FROM data');
    return res.rows;
  } catch (error) {
    console.error('Database query failed:', error);
    throw error;
  } finally {
    await client.end(); // クリーンアップ
  }
}

fetchData().then(data => {
  console.log('Data:', data);
}).catch(error => {
  console.error('Error:', error);
});

この例では、データベース接続を開いた後、必ずfinallyブロックで接続を閉じています。

以上のベストプラクティスを守ることで、非同期処理をより安全かつ効率的に実装することができます。次に、Async/Awaitの使用時によくある問題とその対処法について解説します。

よくある問題とその対処法

Async/Awaitを使用する際には、いくつかのよくある問題が発生することがあります。ここでは、そのような問題とその対処法について解説します。

問題1: Unhandled Promise Rejection

非同期関数内でエラーが発生した場合に、Promiseが拒否されることがあります。これを適切に処理しないと、Unhandled Promise Rejectionが発生し、アプリケーションがクラッシュする可能性があります。

対処法

すべてのAsync関数内でtry...catchブロックを使用し、エラーをキャッチして処理することが重要です。また、呼び出し元でcatchメソッドを使用してエラーを処理することも忘れないようにしましょう。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch data failed:', error);
    throw error;
  }
}

fetchData().catch(error => {
  console.error('Error caught in fetchData:', error);
});

問題2: 並行処理の失敗

複数の非同期操作を並行して実行する場合、いずれかの操作が失敗すると、他の操作がキャンセルされる可能性があります。

対処法

Promise.allを使用して並行処理を行う場合、各操作が個別にエラーを処理するようにするか、すべてのPromiseが解決されるまで待つようにします。

async function fetchAllData() {
  try {
    const [data1, data2, data3] = await Promise.all([
      fetchData1().catch(error => { console.error('fetchData1 failed:', error); return null; }),
      fetchData2().catch(error => { console.error('fetchData2 failed:', error); return null; }),
      fetchData3().catch(error => { console.error('fetchData3 failed:', error); return null; })
    ]);
    return { data1, data2, data3 };
  } catch (error) {
    console.error('Error in fetchAllData:', error);
    throw error;
  }
}

fetchAllData().then(result => {
  console.log('All data fetched:', result);
}).catch(error => {
  console.error('Error:', error);
});

問題3: 競合状態

同時に実行される非同期操作が互いに影響し合う競合状態が発生することがあります。例えば、同じリソースに対して複数の非同期操作が同時にアクセスする場合などです。

対処法

非同期操作が互いに影響しないように、操作の順序や同期を適切に管理します。

let isFetching = false;

async function fetchData() {
  if (isFetching) {
    console.log('Already fetching data');
    return;
  }

  isFetching = true;

  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    console.log('Data fetched:', data);
  } catch (error) {
    console.error('Fetch data failed:', error);
  } finally {
    isFetching = false;
  }
}

fetchData();
fetchData(); // This call will be ignored because data is already being fetched

問題4: パフォーマンスの低下

非同期操作のawaitを過剰に使用すると、パフォーマンスが低下する可能性があります。特に、非同期操作を逐次実行する場合は注意が必要です。

対処法

非同期操作を並行して実行できる場合は、Promise.allを使用してパフォーマンスを向上させます。また、不要なawaitの使用を避けるようにします。

// 悪い例
async function processTasks() {
  const result1 = await task1();
  const result2 = await task2(result1);
  const result3 = await task3(result2);
  return result3;
}

// 良い例
async function processTasks() {
  const result1 = task1(); // awaitを省略
  const result2 = await task2(await result1); // 必要な箇所だけawait
  const result3 = await task3(result2);
  return result3;
}

以上の対処法を実践することで、Async/Awaitを使用する際によくある問題を効果的に解決し、非同期処理をよりスムーズに行うことができます。次に、Async/Awaitの応用例と理解を深めるための演習問題を紹介します。

応用例と演習問題

Async/Awaitの概念と基本的な使い方を理解したところで、実際の開発で役立つ応用例と、理解を深めるための演習問題を紹介します。これらの例と問題を通じて、Async/Awaitを使った非同期処理の実践的なスキルを身に付けましょう。

応用例1: 複数のAPI呼び出しを順次実行する

ユーザー情報とそのユーザーの投稿を取得する非同期処理を実装します。この例では、ユーザー情報を取得した後、そのユーザーの投稿を取得します。

async function fetchUserData(userId) {
  try {
    const userResponse = await fetch(`https://api.example.com/users/${userId}`);
    if (!userResponse.ok) {
      throw new Error('User fetch failed');
    }
    const user = await userResponse.json();

    const postsResponse = await fetch(`https://api.example.com/users/${userId}/posts`);
    if (!postsResponse.ok) {
      throw new Error('Posts fetch failed');
    }
    const posts = await postsResponse.json();

    return { user, posts };
  } catch (error) {
    console.error('Error fetching data:', error);
    throw error;
  }
}

fetchUserData(1).then(result => {
  console.log('User:', result.user);
  console.log('Posts:', result.posts);
}).catch(error => {
  console.error('Error:', error);
});

この応用例では、ユーザーIDを引数として渡し、そのユーザーの情報と投稿を順次取得します。

応用例2: データのバッチ処理

複数のデータをバッチで処理し、それぞれの処理結果をまとめて返す例です。

async function processBatch(dataArray) {
  try {
    const results = await Promise.all(dataArray.map(async (data) => {
      const result = await processData(data);
      return result;
    }));
    return results;
  } catch (error) {
    console.error('Batch processing failed:', error);
    throw error;
  }
}

async function processData(data) {
  // 非同期処理のシミュレーション
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Processed: ${data}`);
    }, 1000);
  });
}

const dataArray = ['data1', 'data2', 'data3'];

processBatch(dataArray).then(results => {
  console.log('Batch results:', results);
}).catch(error => {
  console.error('Error:', error);
});

この応用例では、dataArrayの各データを非同期に処理し、その結果をまとめて返しています。

演習問題

以下の演習問題に取り組んで、Async/Awaitの理解をさらに深めましょう。

問題1: 並行処理

3つの非同期関数task1task2task3があります。それぞれが異なる時間をかけて完了します。これらのタスクを並行して実行し、すべてのタスクが完了するのを待ってから結果を表示する関数runTasksを実装してください。

async function task1() {
  return new Promise((resolve) => setTimeout(() => resolve('Task 1 complete'), 1000));
}

async function task2() {
  return new Promise((resolve) => setTimeout(() => resolve('Task 2 complete'), 2000));
}

async function task3() {
  return new Promise((resolve) => setTimeout(() => resolve('Task 3 complete'), 1500));
}

async function runTasks() {
  // ここに実装
}

runTasks().then(results => {
  console.log('All tasks completed:', results);
}).catch(error => {
  console.error('Error:', error);
});

問題2: エラーハンドリング

以下のコードは、APIからデータを取得する非同期関数fetchDataです。この関数が失敗した場合に、適切なエラーメッセージをコンソールに表示するようにしてください。

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  if (!response.ok) {
    throw new Error('HTTP error! status: ' + response.status);
  }
  const data = await response.json();
  return data;
}

fetchData().then(data => {
  console.log('Data:', data);
}).catch(error => {
  // ここに実装
});

問題3: 非同期ループ

以下のデータリストを使って、各データを非同期に処理する関数processDataListを実装してください。各データの処理結果をリストとして返すようにしてください。

const dataList = [1, 2, 3, 4, 5];

async function processDataList(dataList) {
  // ここに実装
}

processDataList(dataList).then(results => {
  console.log('Processed data:', results);
}).catch(error => {
  console.error('Error:', error);
});

これらの演習問題に取り組むことで、Async/Awaitの実践的なスキルを磨き、非同期処理をより効果的に扱えるようになるでしょう。次に、この記事のまとめを行います。

まとめ

本記事では、JavaScriptにおけるAsync/Awaitの基本とその使い方について詳しく解説しました。非同期処理の概念から始まり、Promiseの基本、Async関数の定義方法、Awaitの使い方、エラーハンドリングの方法、そして実用的な応用例や演習問題までをカバーしました。

Async/Awaitを使うことで、非同期処理をよりシンプルで直感的に記述できるようになります。これにより、コードの可読性と保守性が向上し、複雑な非同期操作も簡単に扱えるようになります。また、適切なエラーハンドリングやベストプラクティスを守ることで、信頼性の高いコードを実装することができます。

この記事で紹介した知識と技術を活用し、実際の開発に役立ててください。Async/Awaitを効果的に使いこなし、JavaScriptでの非同期処理をマスターしましょう。

コメント

コメントする

目次