IT・WEB・ゲーム業界の転職に強いR-Stone

転職コラム

JavaScriptのasync/awaitとは?基本的な使い方や処理方法をご紹介

非同期処理は現代のWEB開発で欠かせない要素ですが、実装は時として複雑になりがちです。そこで注目されているのが、JavaScriptのasync/await構文です。

本記事では、async/awaitの基本的な概念から実際の使用方法、さらにはPromiseとの比較や適切な使い分けまで、幅広く解説します。

async/awaitを使いこなせば、より読みやすく保守しやすい非同期処理のコードを書くことができるでしょう。

JavaScriptのasync/awaitとは

JavaScriptのasync/awaitは、非同期処理を簡潔に記述するための構文で、ECMAScript 2017(ES8)で導入されました。

従来の非同期処理では、コールバック関数やPromiseチェーンを使用していましたが、一文が長くなりがちでした。async/awaitは従来と比べ、コードがより読みやすくなり、保守性も向上します。

また、async/awaitを使うと、try-catch文を使って、同期処理と同じような方法でエラーをキャッチできるため、エラー処理がより直感的になります。

Promiseとの違い

基本的な非同期処理の仕組みはPromiseと同じですが、コードの見た目と書き方が大きく改善されています。

例えば、Promiseを使用した場合のコードは以下のようになります。

function fetchData() {

  return fetch(‘https://api.example.com/data’)

    .then(response => response.json())

    .then(data => {

      console.log(data);

    })

    .catch(error => {

      console.error(‘エラーが発生しました:’, error);

    });

}

上記の例をみると、メソッドチェーンが連なり、全体で大きなreturn文となっています。

一方、async/awaitを使用すると、同じ処理を以下のように書けます。

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);

  }

}

通常の同期処理と同じように個別の文として書けるため、処理の追加や変更がしやすく、例外処理も明確です。

async/awaitの基本的な使い方

本項では、async/awaitの基本的な使い方を見ていきましょう。

非同期関数の宣言

関数の前にasyncキーワードを付けて、非同期関数を宣言します。

async function fetchData() {

  // 非同期処理

}

await演算子の使用

非同期処理の結果を待つ際にawaitキーワードを使います。

awaitは非同期関数でのみ使用できます。

async function fetchData() {

  const response = await fetch(‘https://api.example.com/data’); // APIの応答を待つ

  const data = await response.json();   // JSONとして解釈した結果を待つ

  return data;

}

非同期関数の呼び出し

非同期関数は通常の関数と同じように呼び出せますが、結果はPromiseとして返されるため、非同期関数の外で結果を使うには、then()を使います。

fetchData().then(data =>// Promise.then()を使用してfetchData()の結果を処理

  console.log(data);

});

エラー処理

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);

  }

}

async/awaitでの処理方法

async/awaitでは、さまざまな非同期処理を扱えます。

代表的な処理方法を見ていきましょう。

逐次実行

非同期処理を順番に実行する場合、以下のように記述します。

async function sequentialExecution() {

  const result1 = await asyncFunction1();

  const result2 = await asyncFunction2(result1);

  const result3 = await asyncFunction3(result2);

  return result3;

}

各処理が完了するまで次の処理を待機するため、処理時間は合計になります。

並列実行

複数の非同期処理を同時に実行する場合、Promise.all()を使用します。

async function parallelExecution() {

  const [result1, result2, result3] = await Promise.all([

    asyncFunction1(),

    asyncFunction2(),

    asyncFunction3()

  ]);

  return { result1, result2, result3 };

}

並列実行では、処理時間は最も遅い処理の時間になるため、逐次実行よりも高速です。

タイムアウト処理

指定した時間内に処理が完了しない場合、Promise.race()を使用し、エラーにできます。

async function timeoutExecution(ms) {

  const timeout = new Promise((_, reject) =>

    setTimeout(() => reject(new Error(‘Timeout’)), ms)  // 指定時間ms後エラーを発生する

  );

  try {

    return await Promise.race([asyncFunction(), timeout]);  // 一番早く成功・失敗した状態に決定する

  } catch (error) { // 時間までにtimeout以外のプロミスが成功しないと、エラーとなる。

    console.error(‘時間内に処理が完了しませんでした:’, error);

  }

}

非同期ループ

配列の要素に対して非同期処理をおこなう場合、for…of文とasync/awaitを組み合わせます。

async function asyncLoop(items) {

  const results = [];

  for (const item of items) {   // itemsの各要素に対して順番に

    const result = await asyncFunction(item);   // 非同期処理の結果を待つ

    results.push(result);   // 処理結果を配列resultに追加する

  }

  return results;

}

async/awaitとPromiseを使い分ける方法

async/awaitとPromiseを適切に使い分けると、非同期処理コードの可読性や保守性を向上させられます。

本項では、使い分けの目安となるポイントを見ていきましょう。

処理の複雑さで選ぶ

単純な非同期処理の場合はPromiseを使用し、複雑な処理や複数の非同期処理を連続しておこなう場合はasync/awaitを使用するのが一般的です。

単純な例

function fetchUserData(userId) {  // 単純な例:Promiseを使用する

  return fetch(`https://api.example.com/users/${userId}`) // APIにアクセス

    .then(response => response.json())

    .then(data => console.log(data))

    .catch(error => console.error(‘Error:’, error));

}

複雑な例

async function fetchUserAndPosts(userId) {

  try { // 複雑な例:複数のAPIの結果を処理するケース async/awaitを使用

    const userData = await fetch(`https://api.example.com/users/${userId}`).then(res => res.json());

    const userPosts = await fetch(`https://api.example.com/users/${userId}/posts`).then(res => res.json());

    const lastPostComments = await fetch(`https://api.example.com/posts/${userPosts[0].id}/comments`).then(res => res.json());

    console.log(‘ユーザーの投稿・コメント:’, [userData, userPosts, lastPostComments]);

  } catch (error) {

    console.error(‘Error:’, error);

  }

}

処理性能で選ぶ

async/awaitは内部でPromiseを使用しているため、性能に大きな差はありません。

ただし、非同期処理を大量に並列で実行する場合、Promise.allなどを使うほうが効率的な場合もあります。

function fetchMultipleUsers(userIds) {  // userIds: 多数のIDを格納した配列

  const promises = userIds.map(id => fetch(`https://api.example.com/users/${id}`).then(res => res.json()));

  return Promise.all(promises)  // userIdsの要素数だけ並列で処理する

    .then(users => console.log(‘ユーザー一覧:’, users))

    .catch(error => console.error(‘Error:’, error));

}

読みやすさで選ぶ

コードの可読性も重要な要素です。

async/awaitを使用した、同期処理のようなコードは理解しやすいです。

async function processOrder(orderId) {  // 注文を処理する関数

  try {

    const order = await fetchOrder(orderId);  // 注文情報の取得を待つ

    const user = await fetchUser(order.userId); // ユーザー情報の取得を待つ

    const payment = await processPayment(order.total, user.paymentMethod);  // 決済処理を待つ

    const shipment = await createShipment(order, user.address); // 配送データ作成を待つ

    console.log(‘注文は正常に処理されました:’, { order, payment, shipment });

  } catch (error) {

    console.error(‘注文処理エラー:’, error);

  }

}

一方で、Promiseチェーンを使うと、処理の依存関係を理解しやすい場合もあります。

function processOrder(orderId) {

  return fetchOrder(orderId)

    .then(order => fetchUser(order.userId).then(user => ({ order, user })))

    .then(({ order, user }) => processPayment(order.total, user.paymentMethod).then(payment => ({ order, user, payment })))

    .then(({ order, user, payment }) => createShipment(order, user.address).then(shipment => ({ order, payment, shipment })))

    .then(result => console.log(‘注文は正常に処理されました:’, result))

    .catch(error => console.error(‘注文処理エラー:’, error));

}

まとめ

async/awaitは、ECMAScript 2017で導入された非同期処理のための構文です。Promiseよりも直感的に書けるため、コードの理解や保守が容易になります。逐次実行、並列実行、タイムアウト処理など、さまざまな処理方法に対応できる柔軟性も魅力です。

非同期処理の効率化に悩んでいる方は、ぜひasync/awaitの導入を検討してみてください。