JavaScriptの非同期ループ処理は、モダンなウェブアプリケーションの開発において欠かせない技術です。非同期処理を適切に扱うことで、ユーザーインターフェースの応答性を高め、効率的なデータ処理を実現できます。しかし、非同期ループ処理は複雑であり、初心者にとって理解しにくいことが多いです。本記事では、JavaScriptにおける非同期ループ処理の基本概念から実装方法までを段階的に解説します。コールバック関数、Promise、async/awaitといった主要な非同期処理の手法を取り上げ、それぞれのメリットとデメリットを比較しながら、具体的なコード例を通じて実践的な知識を身につけることを目指します。最後には、実際のプロジェクトでの応用例や、よくある問題とその解決策、パフォーマンスの最適化方法についても触れ、総合的な理解を深めるための演習問題を提供します。
非同期処理の基本概念
JavaScriptはシングルスレッドで動作するプログラミング言語ですが、非同期処理を使用することで、複数のタスクを効率的に管理することができます。非同期処理の基本概念を理解することは、複雑なアプリケーションの開発において非常に重要です。
シングルスレッドと非同期処理
JavaScriptはシングルスレッドで実行されるため、一度に一つのタスクしか実行できません。これに対して、非同期処理を利用すると、時間のかかるタスクを待つ間に他のタスクを実行することができます。これにより、ユーザーインターフェースの応答性を維持しつつ、効率的に複数の処理を進めることが可能になります。
イベントループとコールスタック
JavaScriptの非同期処理は、イベントループとコールスタックを利用して管理されます。コールスタックは実行中の関数を保持し、イベントループはタスクが完了した後にコールバック関数をコールスタックに追加します。この仕組みにより、非同期タスクの実行とその結果の処理が管理されます。
非同期処理の種類
JavaScriptで利用される非同期処理には以下のようなものがあります:
- コールバック関数:非同期タスクが完了したときに呼び出される関数。
- Promise:非同期タスクの結果を表現するオブジェクト。
- async/await:Promiseをより直感的に扱うための構文。
これらの非同期処理の手法は、それぞれ異なる特性と利点を持っており、適切に使い分けることが求められます。次のセクションでは、これらの手法の詳細と具体的な実装方法について説明していきます。
コールバック関数の使用
コールバック関数は、JavaScriptで非同期処理を実現するための基本的な手法の一つです。非同期タスクが完了した後に実行される関数として定義されます。以下では、コールバック関数を用いた非同期ループ処理の実装方法について解説します。
コールバック関数の基本概念
コールバック関数とは、特定のタスクが完了したときに呼び出される関数です。非同期操作が終了した後に続けて実行される処理を、コールバック関数として定義します。これにより、非同期操作の完了を待つ間、他のコードの実行がブロックされることを避けることができます。
コールバック関数を使った非同期ループの例
次に、コールバック関数を使用した非同期ループ処理の具体例を示します。以下の例では、setTimeout関数を用いて非同期に処理を実行しています。
function asyncLoop(iterations, callback) {
let i = 0;
function loop() {
if (i < iterations) {
setTimeout(() => {
console.log('Iteration ' + i);
i++;
loop();
}, 1000); // 1秒ごとに実行
} else {
callback();
}
}
loop();
}
asyncLoop(5, () => {
console.log('Loop finished');
});
このコードでは、asyncLoop
関数が5回のループを1秒ごとに実行し、ループが完了するとコールバック関数が呼び出されます。
コールバック地獄の回避
コールバック関数は便利ですが、複雑な非同期処理を扱う際には、ネストが深くなりやすく、可読性が低下する「コールバック地獄」に陥ることがあります。この問題を回避するためには、Promiseやasync/awaitを利用することが推奨されます。これについては、次のセクションで詳しく説明します。
コールバック関数を正しく利用することで、非同期タスクを効率的に管理できますが、複雑なシナリオでは、より高度な非同期処理手法を検討することが重要です。
Promiseを使った非同期ループ
Promiseは、JavaScriptにおける非同期処理をより直感的に扱うためのオブジェクトです。非同期タスクの成功または失敗を扱う方法を提供し、コールバック地獄を避けるために役立ちます。ここでは、Promiseを利用した非同期ループ処理の例を紹介します。
Promiseの基本概念
Promiseは、非同期操作が成功した場合にresolve、失敗した場合にrejectを呼び出すことによって結果を表現します。Promiseは次の3つの状態を持ちます:
- Pending(保留中): 初期状態。まだ結果が決定していない。
- Fulfilled(成功): 非同期操作が成功し、結果が得られた状態。
- Rejected(失敗): 非同期操作が失敗し、エラーが発生した状態。
Promiseを使った非同期ループの実装
次に、Promiseを利用して非同期ループを実装する例を示します。以下のコードでは、Promiseを用いて一連の非同期操作を順次実行しています。
function asyncOperation(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Iteration ' + iteration);
resolve();
}, 1000); // 1秒ごとに実行
});
}
function asyncLoop(iterations) {
let promise = Promise.resolve();
for (let i = 0; i < iterations; i++) {
promise = promise.then(() => asyncOperation(i));
}
promise.then(() => {
console.log('Loop finished');
});
}
asyncLoop(5);
このコードでは、asyncOperation
関数がPromiseを返し、1秒ごとに非同期に実行されます。asyncLoop
関数は、各非同期操作を順次実行し、最後に「Loop finished」を出力します。
Promiseチェーンの利点
Promiseチェーンを利用することで、非同期操作の順序を明示的に制御でき、コールバック関数のネストを避けることができます。これにより、コードの可読性が向上し、デバッグが容易になります。また、エラーハンドリングも簡単に行えるようになります。次のセクションでは、さらに簡潔に非同期処理を実装できるasync/awaitについて解説します。
Promiseを使用することで、非同期ループ処理の管理がより直感的かつ効率的になります。次のセクションでは、async/awaitを用いた非同期ループ処理の実装方法について説明します。
async/awaitを用いた実装
async/awaitは、Promiseをより簡潔に扱うための構文で、非同期処理を同期的なコードのように記述することができます。これにより、複雑な非同期処理でも可読性が高くなります。以下では、async/awaitを用いた非同期ループ処理の実装方法について解説します。
async/awaitの基本概念
async/awaitは、Promiseを使った非同期処理をシンプルに記述できる構文です。async
関数はPromiseを返し、その中でawait
を使うことで、非同期処理が完了するまで待機することができます。
async/awaitを使った非同期ループの実装
次に、async/awaitを用いて非同期ループを実装する例を示します。以下のコードでは、async関数を使用して1秒ごとに非同期に処理を実行しています。
function asyncOperation(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Iteration ' + iteration);
resolve();
}, 1000); // 1秒ごとに実行
});
}
async function asyncLoop(iterations) {
for (let i = 0; i < iterations; i++) {
await asyncOperation(i);
}
console.log('Loop finished');
}
asyncLoop(5);
このコードでは、asyncOperation
関数がPromiseを返し、1秒ごとに非同期に実行されます。asyncLoop
関数は、for
ループ内でawait
を使って各非同期操作の完了を待ちます。全てのループが完了した後、「Loop finished」を出力します。
async/awaitの利点
async/awaitを使用することで、以下の利点があります:
- コードの可読性:非同期処理を同期的なコードのように記述できるため、可読性が向上します。
- エラーハンドリング:
try...catch
ブロックを用いることで、エラーハンドリングが簡単になります。 - デバッグの容易さ:デバッグツールでステップ実行する際に、非同期処理が同期的に見えるため、トラブルシューティングが容易です。
エラーハンドリングの例
次に、async/awaitを使った非同期ループ処理にエラーハンドリングを追加する例を示します。
async function asyncLoop(iterations) {
try {
for (let i = 0; i < iterations; i++) {
await asyncOperation(i);
}
console.log('Loop finished');
} catch (error) {
console.error('An error occurred:', error);
}
}
asyncLoop(5);
このコードでは、try...catch
ブロックを用いて、非同期処理中に発生する可能性のあるエラーをキャッチし、適切に処理しています。
async/awaitを使用することで、非同期ループ処理の実装が大幅に簡略化され、可読性とメンテナンス性が向上します。次のセクションでは、非同期ループ処理におけるエラーハンドリングの詳細について説明します。
非同期ループのエラーハンドリング
非同期処理におけるエラーハンドリングは、アプリケーションの信頼性と安定性を確保するために不可欠です。特に非同期ループ処理では、複数の非同期タスクが連続して実行されるため、エラーが発生した場合の対処方法を事前に考慮しておく必要があります。ここでは、Promiseとasync/awaitを用いたエラーハンドリングの方法を解説します。
Promiseを用いたエラーハンドリング
Promiseを利用した非同期処理では、catch
メソッドを使ってエラーを処理します。以下の例では、非同期ループ内でエラーが発生した場合にcatch
メソッドでエラーをキャッチして処理しています。
function asyncOperation(iteration) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.8) {
reject('Error in iteration ' + iteration);
} else {
console.log('Iteration ' + iteration);
resolve();
}
}, 1000); // 1秒ごとに実行
});
}
function asyncLoop(iterations) {
let promise = Promise.resolve();
for (let i = 0; i < iterations; i++) {
promise = promise.then(() => asyncOperation(i)).catch((error) => {
console.error(error);
// 必要に応じて再試行やループの中断などの処理を行う
});
}
promise.then(() => {
console.log('Loop finished');
});
}
asyncLoop(5);
このコードでは、asyncOperation
関数内でランダムにエラーが発生するようになっており、catch
メソッドでエラーをキャッチして処理しています。
async/awaitを用いたエラーハンドリング
async/awaitを使用する場合、try...catch
ブロックを用いてエラーハンドリングを行います。以下の例では、非同期ループ内でエラーが発生した場合にtry...catch
ブロックでエラーをキャッチして処理しています。
async function asyncOperation(iteration) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.8) {
reject('Error in iteration ' + iteration);
} else {
console.log('Iteration ' + iteration);
resolve();
}
}, 1000); // 1秒ごとに実行
});
}
async function asyncLoop(iterations) {
try {
for (let i = 0; i < iterations; i++) {
await asyncOperation(i);
}
console.log('Loop finished');
} catch (error) {
console.error('An error occurred:', error);
// 必要に応じて再試行やループの中断などの処理を行う
}
}
asyncLoop(5);
このコードでは、asyncOperation
関数内でランダムにエラーが発生するようになっており、try...catch
ブロックでエラーをキャッチして処理しています。
エラーハンドリングのベストプラクティス
非同期ループ処理におけるエラーハンドリングのベストプラクティスとして、以下の点に注意することが重要です:
- エラーのログ出力:エラーが発生した際には、詳細なログを出力して原因を特定しやすくする。
- 再試行ロジック:一時的なエラーの場合、再試行ロジックを実装してリトライを行う。
- ユーザー通知:エラーが発生した際には、ユーザーに適切な通知を行い、問題の存在を知らせる。
これらのベストプラクティスを適用することで、非同期ループ処理におけるエラーハンドリングが強化され、アプリケーションの信頼性が向上します。次のセクションでは、非同期ループのパフォーマンスを最適化する方法について説明します。
パフォーマンスの最適化
非同期ループ処理のパフォーマンスを最適化することは、アプリケーションの効率と応答性を向上させるために重要です。ここでは、非同期ループのパフォーマンスを最適化するためのテクニックを紹介します。
非同期処理の並列実行
非同期タスクを直列で実行する代わりに、並列で実行することで全体の処理時間を短縮できます。Promise.allを利用して複数の非同期タスクを並列実行する例を示します。
function asyncOperation(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Iteration ' + iteration);
resolve();
}, 1000); // 1秒ごとに実行
});
}
async function parallelAsyncLoop(iterations) {
const promises = [];
for (let i = 0; i < iterations; i++) {
promises.push(asyncOperation(i));
}
await Promise.all(promises);
console.log('Parallel loop finished');
}
parallelAsyncLoop(5);
このコードでは、5つの非同期タスクが並列に実行され、全てのタスクが完了すると「Parallel loop finished」が出力されます。
処理のバッチ化
大量の非同期タスクを一度に実行すると、パフォーマンスが低下する可能性があります。バッチ処理を行うことで、適切な数のタスクを同時に実行し、リソースの過負荷を防ぐことができます。
async function batchAsyncLoop(iterations, batchSize) {
for (let i = 0; i < iterations; i += batchSize) {
const batchPromises = [];
for (let j = 0; j < batchSize && (i + j) < iterations; j++) {
batchPromises.push(asyncOperation(i + j));
}
await Promise.all(batchPromises);
}
console.log('Batch loop finished');
}
batchAsyncLoop(20, 5);
このコードでは、20回の非同期タスクを5つずつのバッチで実行し、各バッチが完了すると次のバッチに進みます。
不要な非同期処理の回避
不要な非同期処理を回避することで、パフォーマンスを向上させることができます。例えば、結果がキャッシュ可能な場合は、キャッシュを利用して無駄な非同期処理を避けます。
const cache = new Map();
async function cachedAsyncOperation(iteration) {
if (cache.has(iteration)) {
return cache.get(iteration);
}
const result = await asyncOperation(iteration);
cache.set(iteration, result);
return result;
}
async function optimizedAsyncLoop(iterations) {
for (let i = 0; i < iterations; i++) {
await cachedAsyncOperation(i);
}
console.log('Optimized loop finished');
}
optimizedAsyncLoop(5);
このコードでは、キャッシュを利用して同じ非同期操作が繰り返し実行されるのを防ぎ、パフォーマンスを最適化しています。
遅延処理の利用
一定時間ごとに非同期タスクを実行することで、リソースの競合を減らし、パフォーマンスを最適化します。以下の例では、setTimeoutを利用して非同期タスクの実行を遅延させています。
async function delayedAsyncOperation(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Delayed iteration ' + iteration);
resolve();
}, 1000); // 1秒ごとに実行
});
}
async function delayedAsyncLoop(iterations) {
for (let i = 0; i < iterations; i++) {
await delayedAsyncOperation(i);
}
console.log('Delayed loop finished');
}
delayedAsyncLoop(5);
このコードでは、各非同期タスクが1秒ごとに実行され、システムリソースの競合を減らしながら処理が行われます。
これらのテクニックを利用することで、非同期ループ処理のパフォーマンスを最適化し、より効率的なアプリケーションを開発することができます。次のセクションでは、非同期ループ処理を活用した具体的なプロジェクト例を紹介します。
実際の使用例
非同期ループ処理は、さまざまなプロジェクトで実用的に利用されます。ここでは、非同期ループ処理を活用した具体的なプロジェクト例をいくつか紹介します。
ウェブスクレイピング
ウェブスクレイピングは、ウェブサイトからデータを抽出する技術です。大量のページをスクレイピングする場合、非同期ループ処理を利用することで、効率的にデータを取得することができます。
const axios = require('axios');
async function fetchPageData(url) {
try {
const response = await axios.get(url);
console.log(`Fetched data from ${url}`);
return response.data;
} catch (error) {
console.error(`Error fetching data from ${url}:`, error);
}
}
async function scrapeWebsites(urls) {
const results = [];
for (const url of urls) {
const data = await fetchPageData(url);
results.push(data);
}
console.log('All data fetched');
return results;
}
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
];
scrapeWebsites(urls);
このコードでは、axios
を用いて非同期にウェブページのデータを取得し、全てのページのデータが取得されると「All data fetched」が出力されます。
APIリクエストのバッチ処理
大量のAPIリクエストを一度に送信する場合、非同期ループ処理を活用することで効率的にリクエストを管理できます。以下の例では、バッチ処理を用いてAPIリクエストを効率的に実行します。
async function fetchApiData(apiUrl) {
try {
const response = await fetch(apiUrl);
const data = await response.json();
console.log(`Fetched data from ${apiUrl}`);
return data;
} catch (error) {
console.error(`Error fetching data from ${apiUrl}:`, error);
}
}
async function batchApiRequests(apiUrls, batchSize) {
for (let i = 0; i < apiUrls.length; i += batchSize) {
const batch = apiUrls.slice(i, i + batchSize).map(fetchApiData);
await Promise.all(batch);
}
console.log('All API requests completed');
}
const apiUrls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3',
// ...more URLs
];
batchApiRequests(apiUrls, 2);
このコードでは、指定されたバッチサイズでAPIリクエストを並列実行し、全てのリクエストが完了すると「All API requests completed」が出力されます。
ファイル処理
ファイルの読み書きなどのI/O操作も非同期ループ処理を活用することで効率化できます。以下の例では、複数のファイルを非同期に読み込み、内容を処理しています。
const fs = require('fs').promises;
async function readFileContent(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
console.log(`Read file: ${filePath}`);
return content;
} catch (error) {
console.error(`Error reading file ${filePath}:`, error);
}
}
async function processFiles(filePaths) {
const fileContents = [];
for (const filePath of filePaths) {
const content = await readFileContent(filePath);
fileContents.push(content);
}
console.log('All files read');
return fileContents;
}
const filePaths = [
'./file1.txt',
'./file2.txt',
'./file3.txt'
];
processFiles(filePaths);
このコードでは、複数のファイルを非同期に読み込み、その内容を処理しています。全てのファイルの読み込みが完了すると「All files read」が出力されます。
これらの具体的なプロジェクト例を通じて、非同期ループ処理の実用的な利用方法を理解することができます。次のセクションでは、非同期ループ処理に関連するよくある問題とその解決策について説明します。
よくある問題とその解決策
非同期ループ処理を実装する際には、いくつかのよくある問題に直面することがあります。ここでは、非同期ループ処理に関連する一般的な問題とその解決策について説明します。
問題1: コールバック地獄
コールバック関数を多用することで、コードが深くネストされてしまい、可読性が低下する「コールバック地獄」が発生することがあります。
解決策
Promiseやasync/awaitを使用することで、コールバック地獄を回避できます。これにより、コードの可読性が向上し、メンテナンスが容易になります。
// コールバック地獄の例
asyncOperation1((result1) => {
asyncOperation2(result1, (result2) => {
asyncOperation3(result2, (result3) => {
// 処理
});
});
});
// async/awaitを使用した解決策
async function processAsyncOperations() {
const result1 = await asyncOperation1();
const result2 = await asyncOperation2(result1);
const result3 = await asyncOperation3(result2);
// 処理
}
processAsyncOperations();
問題2: エラーハンドリングの不足
非同期処理中にエラーが発生すると、適切にハンドリングしないと、アプリケーションが予期せず終了したり、意図しない動作をする可能性があります。
解決策
Promiseのcatch
メソッドやasync/awaitのtry...catch
ブロックを用いて、エラーハンドリングを適切に行います。
// Promiseのエラーハンドリング
asyncOperation().then((result) => {
// 処理
}).catch((error) => {
console.error('Error:', error);
});
// async/awaitのエラーハンドリング
async function processAsyncOperations() {
try {
const result = await asyncOperation();
// 処理
} catch (error) {
console.error('Error:', error);
}
}
processAsyncOperations();
問題3: 非同期処理の競合
複数の非同期処理が競合して、意図しない順序で実行されたり、データが破損することがあります。
解決策
Promise.allを使用して、複数の非同期処理を並列実行する際に、全ての処理が完了するまで待つことで競合を防ぎます。また、必要に応じて排他制御を実装します。
async function processMultipleOperations() {
const promises = [asyncOperation1(), asyncOperation2(), asyncOperation3()];
const results = await Promise.all(promises);
// 処理
}
processMultipleOperations();
問題4: パフォーマンスの低下
大量の非同期タスクを直列で実行すると、全体の処理時間が長くなり、パフォーマンスが低下することがあります。
解決策
非同期タスクを並列実行するか、バッチ処理を用いることでパフォーマンスを最適化します。
// 並列実行の例
async function parallelAsyncOperations() {
const promises = [];
for (let i = 0; i < 5; i++) {
promises.push(asyncOperation(i));
}
await Promise.all(promises);
console.log('All operations completed');
}
parallelAsyncOperations();
// バッチ処理の例
async function batchAsyncOperations(batchSize) {
const totalOperations = 20;
for (let i = 0; i < totalOperations; i += batchSize) {
const batchPromises = [];
for (let j = 0; j < batchSize && (i + j) < totalOperations; j++) {
batchPromises.push(asyncOperation(i + j));
}
await Promise.all(batchPromises);
}
console.log('All batches completed');
}
batchAsyncOperations(5);
これらの解決策を用いることで、非同期ループ処理における一般的な問題を効果的に対処することができます。次のセクションでは、非同期ループ処理をサポートする外部ライブラリの紹介と利用方法について説明します。
外部ライブラリの活用
非同期ループ処理を効率的に実装するために、外部ライブラリを活用することが有効です。ここでは、非同期処理をサポートする代表的なライブラリとその利用方法を紹介します。
ライブラリ1: Async.js
Async.jsは、非同期処理を簡素化するためのユーティリティライブラリで、非同期タスクの管理に便利な関数を多数提供しています。
インストール
Async.jsはnpmを使ってインストールできます。
npm install async
使用例: eachLimit
eachLimit関数を使用して、非同期ループ処理を制御できます。この関数は、指定された数のタスクを並列に実行し、全てのタスクが完了するまで待機します。
const async = require('async');
function asyncOperation(iteration, callback) {
setTimeout(() => {
console.log('Iteration ' + iteration);
callback(null, iteration);
}, 1000); // 1秒ごとに実行
}
const tasks = [0, 1, 2, 3, 4];
async.eachLimit(tasks, 2, asyncOperation, (err) => {
if (err) {
console.error('Error:', err);
} else {
console.log('All tasks completed');
}
});
このコードでは、eachLimit
関数を使用して最大2つのタスクを並列に実行し、全てのタスクが完了すると「All tasks completed」が出力されます。
ライブラリ2: Bluebird
Bluebirdは、高機能なPromiseライブラリで、標準のPromiseよりも多くの機能を提供します。
インストール
Bluebirdはnpmを使ってインストールできます。
npm install bluebird
使用例: map
Bluebirdのmap関数を使用して、非同期タスクを並列に実行し、結果を収集することができます。
const Bluebird = require('bluebird');
function asyncOperation(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Iteration ' + iteration);
resolve(iteration);
}, 1000); // 1秒ごとに実行
});
}
const tasks = [0, 1, 2, 3, 4];
Bluebird.map(tasks, asyncOperation, { concurrency: 2 })
.then((results) => {
console.log('All tasks completed:', results);
})
.catch((err) => {
console.error('Error:', err);
});
このコードでは、map
関数を使用して最大2つのタスクを並列に実行し、全てのタスクが完了すると結果が出力されます。
ライブラリ3: RxJS
RxJSは、リアクティブプログラミングのためのライブラリで、非同期処理やイベント処理を扱うための強力なツールを提供します。
インストール
RxJSはnpmを使ってインストールできます。
npm install rxjs
使用例: fromとmergeMap
RxJSのfrom
関数とmergeMap
演算子を使用して、非同期タスクを管理します。
const { from } = require('rxjs');
const { mergeMap, toArray } = require('rxjs/operators');
function asyncOperation(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Iteration ' + iteration);
resolve(iteration);
}, 1000); // 1秒ごとに実行
});
}
const tasks = [0, 1, 2, 3, 4];
from(tasks)
.pipe(
mergeMap(asyncOperation, 2),
toArray()
)
.subscribe({
next: (results) => {
console.log('All tasks completed:', results);
},
error: (err) => {
console.error('Error:', err);
}
});
このコードでは、mergeMap
演算子を使用して最大2つのタスクを並列に実行し、全てのタスクが完了すると結果が出力されます。
これらの外部ライブラリを活用することで、非同期ループ処理を効率的かつ効果的に実装することができます。次のセクションでは、非同期ループ処理に関する理解を深めるための演習問題を提供します。
演習問題
非同期ループ処理に関する理解を深めるために、いくつかの演習問題を提供します。これらの問題を解くことで、実践的なスキルを身につけることができます。
問題1: コールバック関数を使った非同期ループ
以下の要件を満たすように、コールバック関数を使って非同期ループ処理を実装してください:
- 1秒ごとに”Hello, World!”を5回出力する。
- ループが完了したら、”Loop finished”を出力する。
function printMessage(iteration, callback) {
setTimeout(() => {
console.log('Hello, World!');
callback();
}, 1000);
}
function asyncLoop(iterations, callback) {
let i = 0;
function loop() {
if (i < iterations) {
printMessage(i, () => {
i++;
loop();
});
} else {
callback();
}
}
loop();
}
asyncLoop(5, () => {
console.log('Loop finished');
});
問題2: Promiseを使った非同期ループ
以下の要件を満たすように、Promiseを使って非同期ループ処理を実装してください:
- 1秒ごとに”Task completed”を3回出力する。
- ループが完了したら、”All tasks done”を出力する。
function asyncTask(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Task completed');
resolve();
}, 1000);
});
}
function promiseLoop(iterations) {
let promise = Promise.resolve();
for (let i = 0; i < iterations; i++) {
promise = promise.then(() => asyncTask(i));
}
promise.then(() => {
console.log('All tasks done');
});
}
promiseLoop(3);
問題3: async/awaitを使った非同期ループ
以下の要件を満たすように、async/awaitを使って非同期ループ処理を実装してください:
- 1秒ごとに”Processing”を4回出力する。
- ループが完了したら、”Processing complete”を出力する。
async function asyncProcessing(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Processing');
resolve();
}, 1000);
});
}
async function asyncAwaitLoop(iterations) {
for (let i = 0; i < iterations; i++) {
await asyncProcessing(i);
}
console.log('Processing complete');
}
asyncAwaitLoop(4);
問題4: 並列処理を使った非同期ループ
以下の要件を満たすように、Promise.allを使って並列処理を実装してください:
- 3つのタスクを並列に実行し、それぞれ2秒後に”Parallel task done”を出力する。
- 全てのタスクが完了したら、”All parallel tasks done”を出力する。
function parallelTask(iteration) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Parallel task done');
resolve();
}, 2000);
});
}
function parallelLoop(tasks) {
Promise.all(tasks.map(parallelTask)).then(() => {
console.log('All parallel tasks done');
});
}
parallelLoop([1, 2, 3]);
問題5: エラーハンドリングを追加した非同期ループ
以下の要件を満たすように、エラーハンドリングを追加した非同期ループ処理を実装してください:
- 1秒ごとに”Iteration X”を5回出力する。ただし、ランダムにエラーが発生する。
- エラーが発生した場合は”Error occurred: [エラーメッセージ]”を出力し、ループを中断する。
function errorProneTask(iteration) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.7) {
reject('Error at iteration ' + iteration);
} else {
console.log('Iteration ' + iteration);
resolve();
}
}, 1000);
});
}
async function errorHandlingLoop(iterations) {
try {
for (let i = 0; i < iterations; i++) {
await errorProneTask(i);
}
console.log('Loop finished without errors');
} catch (error) {
console.error('Error occurred:', error);
}
}
errorHandlingLoop(5);
これらの演習問題を通じて、非同期ループ処理の理解を深め、実践的なスキルを向上させてください。次のセクションでは、本記事のまとめを行います。
まとめ
本記事では、JavaScriptにおける非同期ループ処理の基本概念と実装方法について解説しました。コールバック関数、Promise、async/awaitといった主要な非同期処理手法を取り上げ、それぞれの利点と適用方法を具体的なコード例を通じて紹介しました。
さらに、非同期ループ処理のエラーハンドリングの重要性と実装方法についても説明し、よくある問題とその解決策、パフォーマンスの最適化テクニックについて詳しく解説しました。最後に、実際のプロジェクト例や外部ライブラリの活用方法、理解を深めるための演習問題を提供しました。
非同期ループ処理は、モダンなウェブアプリケーションの開発において非常に重要なスキルです。この記事を通じて、非同期ループ処理の基礎から応用までの知識を身につけ、実際のプロジェクトに活用できるようになることを目指しています。今後の開発において、非同期処理を効率的に管理し、アプリケーションのパフォーマンスと信頼性を向上させてください。
コメント