【実践編】Node.jsでPromise.allとforEachを駆使して高速なWebアプリケーション開発を実現
Node.jsにおける非同期処理とPromise.all、forEachの使い分け
Node.jsは非同期処理を扱う上で非常に便利ですが、複数の非同期処理を同時に処理したり、処理完了後にまとめて処理を実行したいケースも少なくありません。そこで今回は、非同期処理を効率的に扱うためのPromise.all
とforEach
の使い分けについて、分かりやすく解説します。
非同期処理とコールバック
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 }]
});
上記の例では、fetchData1
とfetchData2
の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.all
とforEach
は、それぞれ異なる目的で使用されます。
- 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/await
とfor
ループを組み合わせる方法は、シンプルな構文で分かりやすく、コードの可読性も高くなります。
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