【実践編】Node.jsでPromise.allとforEachを駆使して高速なWebアプリケーション開発を実現

2024-06-27

Node.jsにおける非同期処理とPromise.all、forEachの使い分け

Node.jsは非同期処理を扱う上で非常に便利ですが、複数の非同期処理を同時に処理したり、処理完了後にまとめて処理を実行したいケースも少なくありません。そこで今回は、非同期処理を効率的に扱うためのPromise.allforEachの使い分けについて、分かりやすく解説します。

非同期処理とコールバック

Node.jsの非同期処理は、一般的にコールバック関数と呼ばれる関数を使って実装されます。コールバック関数は、非同期処理が完了した後に呼び出される関数です。

function fetchData(callback) {
  setTimeout(() => {
    const data = { name: 'Taro', age: 30 };
    callback(data);
  }, 1000);
}

fetchData((data) => {
  console.log(data); // { name: 'Taro', age: 30 }
});

上記の例では、fetchData関数は非同期処理として1秒後にデータを取得し、完了後にコールバック関数を実行します。コールバック関数内で、取得したデータを利用することができます。

Promiseは、非同期処理をより扱いやすくするための構文です。Promiseオブジェクトは、非同期処理の状態(完了、失敗)と、その処理結果を保持します。

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { name: 'Taro', age: 30 };
      resolve(data);
    }, 1000);
  });
};

fetchData().then((data) => {
  console.log(data); // { name: 'Taro', age: 30 }
}).catch((error) => {
  console.error(error);
});

上記の例では、fetchData関数はPromiseオブジェクトを返します。thenメソッドはPromiseが完了した際に呼び出されるメソッドで、catchメソッドはPromiseが失敗した際に呼び出されるメソッドです。

Promise.allは、複数のPromiseオブジェクトを同時に処理し、全て完了した後にまとめて処理を実行するための関数です。引数としてPromiseオブジェクトの配列を渡し、全てのPromiseが完了したら、その結果を配列として返します。

const fetchData1 = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ name: 'Taro', age: 30 });
    }, 1000);
  });
};

const fetchData2 = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ name: 'Jiro', age: 25 });
    }, 500);
  });
};

Promise.all([fetchData1(), fetchData2()]).then((data) => {
  console.log(data); // [{ name: 'Taro', age: 30 }, { name: 'Jiro', age: 25 }]
});

上記の例では、fetchData1fetchData2のPromiseオブジェクトを配列に格納し、Promise.allに渡しています。Promise.allは、全てのPromiseが完了したら、それぞれの結果を配列としてthenメソッドに渡します。

forEachは、配列の各要素に対して順番に処理を実行するための関数です。引数として配列と、各要素に対して実行する処理を定義した関数(コールバック関数)を渡します。

const data = [{ name: 'Taro', age: 30 }, { name: 'Jiro', age: 25 }];

data.forEach((item) => {
  console.log(item); // { name: 'Taro', age: 30 }, { name: 'Jiro', age: 25 }
});

上記の例では、data配列の各要素に対して、console.log関数で要素を出力しています。

Promise.allforEachは、それぞれ異なる目的で使用されます。

  • Promise.all: 複数の非同期処理を同時に処理し、全て完了した後にまとめて処理を実行する場合に使用します。
  • forEach: 配列の各要素に対して順番に処理を実行する場合に使用します。

例:非同期処理の同時実行と結果の集計

以下は、複数の非同期処理を同時に実行し、それぞれの処理結果を集計する例です




const fetchData = async (userId) => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data = await response.json();
  return data;
};

const getUserIds = () => {
  return [1, 2, 3];
};

const async = async () => {
  const userIds = getUserIds();
  const promises = userIds.map((userId) => fetchData(userId));
  const results = await Promise.all(promises);
  console.log(results);
};

async();

補足

  • awaitキーワードは、Promiseオブジェクトが解決されるのを待って、その値を取得するためのものです。
  • Promise.allは、引数にPromiseオブジェクトの配列を渡し、全てのPromiseが完了したら、その結果を配列として返します。
  • 上記の例は、あくまでも一例です。具体的な処理内容に合わせて、コードを適宜修正してください。



Promise.allとforEach以外の並列処理方法

async/awaitforループを組み合わせる方法は、シンプルな構文で分かりやすく、コードの可読性も高くなります。

const fetchData = async (userId) => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data = await response.json();
  return data;
};

const getUserIds = () => {
  return [1, 2, 3];
};

const async = async () => {
  const userIds = getUserIds();
  const results = [];
  for (const userId of userIds) {
    const userData = await fetchData(userId);
    results.push(userData);
  }
  console.log(results);
};

async();

この例では、forループでユーザーID配列を順番に処理し、それぞれに対してfetchDataを非同期に実行します。awaitを使って非同期処理の結果を待ってから、results配列に格納しています。

Promise.allSettledは、Promise.allと似ていますが、全てのPromiseが完了または失敗した後に、それぞれのPromiseの結果を配列として返します。エラーが発生しても処理を続行したい場合に有効です。

const fetchData = async (userId) => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  if (!response.ok) {
    throw new Error(`Failed to fetch user data: ${userId}`);
  }
  const data = await response.json();
  return data;
};

const getUserIds = () => {
  return [1, 2, 3];
};

const async = async () => {
  const userIds = getUserIds();
  const promises = userIds.map((userId) => fetchData(userId));
  const results = await Promise.allSettled(promises);
  console.log(results);
};

async();

この例では、fetchData内でエラーが発生した場合に例外をスローし、Promise.allSettledでエラーも含めて全てのPromiseの結果を取得しています。

再帰処理は、非同期処理を再帰的に呼び出すことで並列処理を実現する方法です。複雑になりがちですが、柔軟性のある処理が可能になります。

const fetchData = async (userId, results) => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data = await response.json();
  results.push(data);

  if (userIds.length > 0) {
    const nextUserId = userIds.shift();
    await fetchData(nextUserId, results);
  }
};

const getUserIds = () => {
  return [1, 2, 3];
};

const async = async () => {
  const userIds = getUserIds();
  const results = [];
  await fetchData(userIds[0], results);
  console.log(results);
};

async();

この例では、fetchData関数を再帰的に呼び出し、処理対象のユーザーIDがなくなるまで非同期処理を続行します。

ライブラリ

並列処理をより簡単に扱えるように、様々なライブラリが提供されています。代表的なライブラリとしては、以下のようなものがあります。

    これらのライブラリは、Promise以外にも様々な機能を提供しており、複雑な並列処理をより効率的に記述することができます。

    Promise.allとforEach以外にも、様々な方法で並列処理を実行することができます。それぞれの方法の特徴を理解し、状況に応じて適切な方法を選択することが重要です。


    javascript node.js asynchronous


    Object.assign vs スプレッド構文:JavaScript オブジェクトのマージ方法徹底比較

    Object. assign は、ターゲットオブジェクトにソースオブジェクトのプロパティをコピーするメソッドです。このコードでは、obj1 と obj2 のプロパティを mergedObj という新しいオブジェクトにマージしています。Object...


    JavaScript、HTML、AngularJS で ui-sref を使ってコントローラーにパラメータを渡す方法

    AngularJS の UI-Router で、ui-sref ディレクティブを使用して、ステート遷移時にコントローラーにパラメータを渡す方法について説明します。例以下の例では、user/:id というステートに遷移し、id パラメータをコントローラーに渡します。...


    Node.js で S3 の署名付き URL を安全に操作: 包括的なチュートリアル

    Amazon S3 の署名付き URL は、一時的にオブジェクトへのアクセスを許可する安全な方法です。この URL を使用すると、認証なしでオブジェクトをダウンロードしたり、アップロードしたりできます。これは、特に機密性の高いデータへのアクセスを制御する必要がある場合に役立ちます。...


    【決定版】JavaScript, TypeScript, ECMAScript 5 でアクセサーを使いこなすためのチュートリアル

    アクセサーのしくみアクセサーは、getterとsetterの2つのメソッドで構成されます。getter: プロパティの値を取得するメソッドです。通常のプロパティ参照のように object. propertyName と記述するだけで呼び出されます。...


    Angular 4 のフォームコントロールで値アクセサーを使用する: "No value accessor for form control" エラーを解決する方法

    この問題を解決するには、以下の方法があります。適切な値アクセサーを設定するフォームコントロールには、値アクセサーを設定する必要があります。値アクセサーは、フォームコントロールと HTML 要素間のデータのやり取りを仲介します。Angular 4 には、いくつかのデフォルトの値アクセサーが用意されています。...