TypeScript非同期処理パターン完全ガイド:エラーハンドリングから並行処理まで

TypeScript非同期処理パターン完全ガイド:エラーハンドリングから並行処理まで
TypeScriptにおける非同期処理の基礎
TypeScriptでの非同期処理は、ウェブアプリケーション開発において避けて通れない重要な概念です。APIリクエスト、ファイル操作、データベースアクセスなど、多くの操作が非同期的に実行されます。
コールバック関数から始まる非同期処理
JavaScriptの初期の非同期処理はコールバック関数を使って実装されていました。
// コールバックを使った非同期処理の例
function fetchData(callback: (data: any, error?: Error) => void): void {
setTimeout(() => {
try {
const data = { name: "TypeScript", version: "5.0" };
callback(data);
} catch (error) {
callback(null, error as Error);
}
}, 1000);
}
// 使用例
fetchData((data, error) => {
if (error) {
console.error("エラーが発生しました:", error);
return;
}
console.log("データを取得しました:", data);
});
しかし、このアプローチでは複数の非同期処理を連結するとコールバック地獄(Callback Hell)と呼ばれる読みにくく保守しづらいコードになってしまいます。
Promiseによる解決
ES2015でPromiseが導入され、非同期処理の書き方が改善されました。TypeScriptは型安全性を持ってPromiseをサポートしています。
// Promiseを使った非同期処理の例
function fetchData(): Promise<{ name: string; version: string }> {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const data = { name: "TypeScript", version: "5.0" };
resolve(data);
} catch (error) {
reject(error);
}
}, 1000);
});
}
// 使用例
fetchData()
.then(data => {
console.log("データを取得しました:", data);
})
.catch(error => {
console.error("エラーが発生しました:", error);
});
Promiseの主な状態は以下の3つです:
- Pending(保留中): 初期状態、非同期処理の結果はまだ利用できない
- Fulfilled(成功): 非同期処理が正常に完了し、結果が利用可能
- Rejected(失敗): 非同期処理がエラーで失敗
TypeScriptでは、Promiseの返り値の型を明示することで、非同期処理の結果の型を保証できます。これにより、IDEの自動補完や型チェックの恩恵を受けることができます。
Promiseを使った効率的な非同期処理の実装
Promiseは非同期処理を効率的に実装するための強力なツールです。TypeScriptと組み合わせることでさらに堅牢なコードを書くことができます。
Promiseチェーンの構築
Promiseの大きな利点は、.then()
メソッドを使って処理を連鎖させられることです。これにより、複数の非同期処理を順番に実行する「Promiseチェーン」を構築できます。
// Promiseチェーンの例
function fetchUserData(userId: string): Promise<User> {
return fetch(`https://api.example.com/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error(`APIエラー: ${response.status}`);
}
return response.json();
})
.then(data => {
return {
id: data.id,
name: data.name,
email: data.email
} as User;
});
}
// 使用例
fetchUserData("123")
.then(user => {
console.log("ユーザーデータを取得しました:", user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log("ユーザーの投稿を取得しました:", posts);
})
.catch(error => {
console.error("エラーが発生しました:", error);
});
各.then()
の戻り値は新しいPromiseとなるため、非同期処理を連鎖させることができます。
Promise.allを使った並列処理
複数の非同期処理を並行して実行し、すべてが完了したら次の処理に進みたい場合はPromise.all()
が便利です。
// Promise.allの例
async function fetchAllUsersData(userIds: string[]): Promise<User[]> {
const promises = userIds.map(id => fetchUserData(id));
return Promise.all(promises);
}
// 使用例
fetchAllUsersData(["123", "456", "789"])
.then(users => {
console.log("すべてのユーザーデータを取得しました:", users);
})
.catch(error => {
console.error("いずれかのリクエストでエラーが発生しました:", error);
});
Promise.all()
は、渡されたすべてのPromiseが解決されるとそれらの結果を配列で返します。いずれかのPromiseが失敗すると、すぐにエラーをスローします。
Promise.raceを使ったタイムアウト実装
リクエストに時間制限を設けたい場合、Promise.race()
を使ってタイムアウト処理を実装できます。
// タイムアウト関数の定義
function timeout(ms: number): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`操作がタイムアウトしました (${ms}ms)`));
}, ms);
});
}
// Promise.raceを使ったタイムアウト処理の例
function fetchWithTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
return Promise.race([
promise,
timeout(ms)
]);
}
// 使用例
fetchWithTimeout(fetchUserData("123"), 5000)
.then(user => {
console.log("ユーザーデータを取得しました:", user);
})
.catch(error => {
console.error("エラーが発生しました:", error);
});
Promise.race()
は、渡されたPromiseのうち最初に解決または拒否されたものの結果を返します。この特性を利用して、指定した時間内に処理が完了しない場合にタイムアウトさせることができます。
TypeScriptのジェネリック型を活用することで、どのような型の非同期処理でも扱えるようになります。
async/awaitを活用したコードの可読性向上
ES2017で導入されたasync/await
構文は、Promiseベースの非同期コードをさらに読みやすく直感的に書けるようにしました。TypeScriptは完全にこの構文をサポートしています。
async/awaitの基本
async/await
は、非同期処理を同期処理のように書けるようにする構文糖です。async
関数内でawait
キーワードを使用すると、Promiseの解決を待機することができます。
// async/awaitを使用した例
async function fetchUserData(userId: string): Promise<User> {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`APIエラー: ${response.status}`);
}
const data = await response.json();
return {
id: data.id,
name: data.name,
email: data.email
} as User;
}
// 使用例
async function displayUserInfo(userId: string): Promise<void> {
try {
const user = await fetchUserData(userId);
console.log("ユーザーデータを取得しました:", user);
const posts = await fetchUserPosts(user.id);
console.log("ユーザーの投稿を取得しました:", posts);
} catch (error) {
console.error("エラーが発生しました:", error);
}
}
// 関数の実行
displayUserInfo("123").then(() => {
console.log("処理が完了しました");
});
async
が付いた関数は常にPromiseを返します。関数内でreturn
された値は、そのPromiseの解決値になります。
TypeScriptでのasync/awaitの型付け
TypeScriptでは、async
関数の戻り値の型は自動的にPromise<T>
になります。関数が値T
を返す場合、実際の型はPromise<T>
になります。
// 戻り値の型アノテーション
async function fetchData(): Promise<string> {
// 文字列を返しているように見えますが、実際はPromise<string>を返します
return "データ";
}
// これは以下と同等です
function fetchDataPromise(): Promise<string> {
return Promise.resolve("データ");
}
try/catchによるエラーハンドリング
async/await
を使用すると、通常の同期コードと同じようにtry/catch
ブロックでエラーを捕捉できます。
async function safelyFetchData(userId: string): Promise<User | null> {
try {
const user = await fetchUserData(userId);
return user;
} catch (error) {
console.error("ユーザーデータの取得に失敗しました:", error);
// エラーログを送信するなどの処理
return null;
}
}
この方法はPromiseの.catch()
メソッドを使うよりも読みやすく、複数の非同期処理を含むコードでも一箇所でエラーハンドリングできます。
連続した非同期処理
複数の非同期処理を順番に実行する場合、async/await
を使うとコードが非常に読みやすくなります。
async function processUserData(userId: string): Promise<ProcessedData> {
// 順番に実行される非同期処理
const user = await fetchUserData(userId);
const posts = await fetchUserPosts(user.id);
const comments = await fetchPostComments(posts[0].id);
return {
user,
recentPost: posts[0],
recentComments: comments
};
}
Promiseチェーンで書く場合よりもインデントが少なく、流れが理解しやすくなります。
並列処理との組み合わせ
非同期処理を並列に実行しつつasync/await
の読みやすさを維持するには、Promise.all()
などと組み合わせます。
async function fetchDashboardData(userId: string): Promise<Dashboard> {
// 並列に実行される非同期処理
const [user, settings, notifications] = await Promise.all([
fetchUserData(userId),
fetchUserSettings(userId),
fetchUserNotifications(userId)
]);
return {
user,
settings,
notificationCount: notifications.length
};
}
これにより、3つのAPI呼び出しが同時に実行され、すべてが完了してから次の処理に進みます。
実践的なエラーハンドリングパターン
非同期処理においてエラーを適切に処理することは、堅牢なアプリケーションを構築する上で非常に重要です。TypeScriptの型システムを活用することで、より安全なエラーハンドリングが可能になります。
カスタムエラークラスの定義
TypeScriptでは、独自のエラークラスを定義することで、より詳細なエラー情報を扱えます。
// カスタムエラークラスの定義
class APIError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.name = 'APIError';
this.statusCode = statusCode;
// Errorオブジェクトのプロトタイプチェーンを正しく設定するために必要
Object.setPrototypeOf(this, APIError.prototype);
}
isClientError(): boolean {
return this.statusCode >= 400 && this.statusCode < 500;
}
isServerError(): boolean {
return this.statusCode >= 500;
}
}
// 使用例
async function fetchUserData(userId: string): Promise<User> {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new APIError(`ユーザーデータの取得に失敗しました`, response.status);
}
return await response.json();
}
カスタムエラークラスを使用すると、エラーの種類ごとに異なる処理を実装できます。
try-catchとインスタンスチェック
TypeScriptでは、instanceof
演算子を使って捕捉したエラーの種類を確認できます。
async function handleUserRequest(userId: string): Promise<void> {
try {
const user = await fetchUserData(userId);
console.log("ユーザーデータ:", user);
} catch (error) {
if (error instanceof APIError) {
if (error.statusCode === 404) {
console.error(`ユーザーID ${userId} は存在しません`);
} else if (error.isServerError()) {
console.error(`サーバーエラー: ${error.message}`);
// サーバーエラーを監視サービスに報告するなど
reportToMonitoringService(error);
}
} else if (error instanceof TypeError) {
console.error(`型エラー: ${error.message}`);
} else {
console.error(`予期しないエラー: ${error}`);
}
}
}
このアプローチにより、エラーの種類に応じて適切な処理を行えます。
Result型パターン
エラーを例外としてではなく、戻り値の一部として扱う「Result型パターン」も効果的です。
// Result型の定義
type Result<T, E = Error> = {
success: true;
value: T;
} | {
success: false;
error: E;
};
// Result型を返す関数
async function safelyFetchUserData(userId: string): Promise<Result<User, APIError>> {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
return {
success: false,
error: new APIError(`ユーザーデータの取得に失敗しました`, response.status)
};
}
const user = await response.json();
return {
success: true,
value: user
};
} catch (error) {
return {
success: false,
error: error instanceof Error
? new APIError(error.message, 500)
: new APIError('不明なエラー', 500)
};
}
}
// 使用例
async function displayUserInfo(userId: string): Promise<void> {
const result = await safelyFetchUserData(userId);
if (result.success) {
// TypeScriptは自動的にresult.valueがUser型であることを認識します
console.log("ユーザー名:", result.value.name);
} else {
// エラーハンドリング
if (result.error.statusCode === 404) {
console.error("ユーザーが見つかりません");
} else {
console.error("エラー:", result.error.message);
}
}
}
Result型パターンを使用すると、エラーハンドリングがより明示的になり、開発者は返り値の型を見るだけでエラーの可能性を考慮する必要があることがわかります。
非同期関数内のエラー伝播
async/await
を使用する場合、エラーはPromiseチェーンと同様に伝播します。
async function processUserWorkflow(userId: string): Promise<void> {
try {
// エラーが発生すると、そのエラーはこのtry/catchブロックでキャッチされます
const user = await fetchUserData(userId);
const posts = await fetchUserPosts(user.id);
const comments = await fetchPostComments(posts[0].id);
console.log("処理成功:", {
user,
postCount: posts.length,
commentCount: comments.length
});
} catch (error) {
console.error("ワークフロー処理中にエラーが発生しました:", error);
// エラーを上位の呼び出し元に再スローすることもできます
throw error;
}
}
この方法では、一連の非同期操作中のいずれかでエラーが発生した場合でも、すべて単一のcatch
ブロックで処理できます。
複数の非同期処理を効率的に管理する方法
実務上、複数の非同期処理を効率的に管理することは頻繁に求められる課題です。TypeScriptを使って複数の非同期処理を整理し、管理する方法を見ていきましょう。
依存関係に基づく処理の整理
非同期処理の依存関係を視覚的に理解するためには、処理のフローを図式化すると効果的です。
// 依存関係に基づく処理の整理例
async function loadApplicationData(userId: string): Promise<AppData> {
// 1. ユーザーデータのロード(他に依存しない)
const userPromise = fetchUserData(userId);
// 2. ユーザーの設定とシステム設定を並行でロード(他に依存しない)
const settingsPromise = fetchUserSettings(userId);
const systemConfigPromise = fetchSystemConfig();
// これらの処理を並行して実行
const [user, settings, systemConfig] = await Promise.all([
userPromise, settingsPromise, systemConfigPromise
]);
// 3. ユーザーとその設定に依存する処理
const [permissions, favorites] = await Promise.all([
fetchUserPermissions(user.role, settings.permissionLevel),
fetchUserFavorites(user.id, settings.favoriteCount)
]);
// 4. 最終的なデータの構築
return {
user,
settings,
permissions,
favorites,
systemConfig
};
}
このようにコードを整理することで、依存関係の理解が容易になり、非同期処理の実行効率も向上します。
非同期処理のキャンセル
TypeScriptとAbortControllerを組み合わせることで、非同期処理をキャンセルできます。
// キャンセル可能な非同期処理の例
async function fetchWithTimeout<T>(
url: string,
options: RequestInit = {},
timeoutMs: number = 5000
): Promise<T> {
// AbortControllerの作成
const controller = new AbortController();
const { signal } = controller;
// タイムアウトのセットアップ
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
// シグナルをfetchオプションに追加
const response = await fetch(url, { ...options, signal });
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.status}`);
}
// レスポンスをJSON形式でパース
return await response.json() as T;
} catch (error) {
// AbortError(タイムアウトによるキャンセル)の場合
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
// その他のエラー
throw error;
} finally {
// タイムアウトタイマーのクリーンアップ
clearTimeout(timeoutId);
}
}
// 使用例
async function loadData(shouldCancel: boolean) {
const controller = new AbortController();
try {
// 非同期処理の開始
const dataPromise = fetch('/api/data', { signal: controller.signal });
if (shouldCancel) {
// ある条件下で処理をキャンセル
controller.abort();
}
const response = await dataPromise;
const data = await response.json();
return data;
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
console.log('Fetch was cancelled');
} else {
console.error('Error fetching data:', error);
}
}
}
キューを使った連続的な非同期処理
同時実行数を制限しながら多数の非同期処理を実行する場合、キューを使うアプローチが効果的です。
// 非同期タスクキューの実装
class AsyncTaskQueue {
private queue: (() => Promise<unknown>)[] = [];
private concurrentLimit: number;
private runningCount = 0;
private isProcessing = false;
constructor(concurrentLimit = 3) {
this.concurrentLimit = concurrentLimit;
}
// タスクをキューに追加
enqueue<T>(task: () => Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
// タスクをラップしてキューに追加
this.queue.push(async () => {
try {
const result = await task();
resolve(result);
return result;
} catch (error) {
reject(error);
throw error;
}
});
// キュー処理がまだ開始されていなければ開始
if (!this.isProcessing) {
this.processQueue();
}
});
}
// キューの処理
private async processQueue(): Promise<void> {
this.isProcessing = true;
while (this.queue.length > 0 && this.runningCount < this.concurrentLimit) {
const task = this.queue.shift();
if (!task) continue;
this.runningCount++;
// タスクを実行し、完了後にrunningCountを減らす
task().finally(() => {
this.runningCount--;
this.processQueue();
});
}
this.isProcessing = this.runningCount > 0 || this.queue.length > 0;
}
// 現在のキューの状態を確認
get status(): { queued: number; running: number } {
return {
queued: this.queue.length,
running: this.runningCount
};
}
}
// 使用例
async function processLargeDataSet(items: string[]): Promise<Record<string, any>> {
const taskQueue = new AsyncTaskQueue(5); // 最大5つの並行タスク
const results: Record<string, any> = {};
const tasks = items.map(item =>
taskQueue.enqueue(async () => {
const data = await fetchDataForItem(item);
results[item] = data;
})
);
// すべてのタスクが完了するまで待機
await Promise.all(tasks);
return results;
}
このアプローチは、APIレート制限などがある場合に特に効果的です。
リトライメカニズムの実装
不安定なネットワーク環境や一時的なサーバーエラーに対処するため、リトライ機能を実装することも重要です。
// 指数バックオフを使ったリトライ関数
async function withRetry<T>(
fn: () => Promise<T>,
options: {
maxRetries?: number;
initialDelay?: number;
maxDelay?: number;
backoffFactor?: number;
retryableErrors?: (error: unknown) => boolean;
} = {}
): Promise<T> {
const {
maxRetries = 3,
initialDelay = 1000,
maxDelay = 30000,
backoffFactor = 2,
retryableErrors = (error) => true // デフォルトではすべてのエラーをリトライ
} = options;
let lastError: unknown;
let delay = initialDelay;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// 最後の試行でエラーが発生した場合、または
// リトライ可能なエラーでない場合はエラーをスロー
if (attempt === maxRetries || !retryableErrors(error)) {
throw error;
}
// 待機時間をログに記録
console.log(`Attempt ${attempt + 1}/${maxRetries} failed. Retrying in ${delay}ms...`);
// 指定された時間だけ待機
await new Promise(resolve => setTimeout(resolve, delay));
// 次回の待機時間を計算(指数バックオフ)
delay = Math.min(delay * backoffFactor, maxDelay);
}
}
// コンパイラのためのフォールバック(実際には到達しない)
throw lastError;
}
// 使用例
async function fetchDataWithRetry(url: string): Promise<any> {
return withRetry(
() => fetch(url).then(res => {
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
return res.json();
}),
{
maxRetries: 3,
retryableErrors: (error) => {
// ネットワークエラーや500系エラーのみリトライ
return error instanceof Error && (
error.message.includes('network') ||
error.message.includes('HTTP error: 5')
);
}
}
);
}
指数バックオフ戦略を使用することで、サーバーに過負荷をかけることなくリトライを実行できます。
パフォーマンスを考慮した並行処理の実装テクニック
TypeScriptで非同期処理を扱う際、パフォーマンスを最適化するためのテクニックを知っておくことは重要です。以下では、並行処理を効率的に実装するための方法を紹介します。
Promise.allSettledの活用
ES2020で導入されたPromise.allSettled()
は、すべてのPromiseが成功または失敗で解決されるまで待機し、それぞれの結果を配列で返します。これにより、一部の処理が失敗しても残りの処理を継続できます。
// Promise.allSettledの例
async function fetchAllUserDataSafely(userIds: string[]): Promise<{
successful: User[];
failed: { id: string; error: Error }[];
}> {
const promises = userIds.map(id =>
fetchUserData(id)
.then(data => ({ status: 'fulfilled' as const, value: data, id }))
.catch(error => ({ status: 'rejected' as const, reason: error, id }))
);
const results = await Promise.all(promises);
const successful: User[] = [];
const failed: { id: string; error: Error }[] = [];
for (const result of results) {
if (result.status === 'fulfilled') {
successful.push(result.value);
} else {
failed.push({ id: result.id, error: result.reason });
}
}
return { successful, failed };
}
// 使用例
async function processUsersWithoutFailingAll(userIds: string[]): Promise<void> {
const results = await fetchAllUserDataSafely(userIds);
console.log(`成功: ${results.successful.length}件, 失敗: ${results.failed.length}件`);
if (results.failed.length > 0) {
console.warn('以下のユーザーデータ取得に失敗しました:', results.failed.map(f => f.id).join(', '));
}
// 成功したデータだけで処理を続行
processSuccessfulUsers(results.successful);
}
バッチ処理による最適化
大量のデータを処理する場合、バッチ処理を使用して段階的に処理することでメモリ使用量を抑えられます。
// バッチ処理の実装
async function processBatches<T, R>(
items: T[],
processFn: (batch: T[]) => Promise<R[]>,
options: { batchSize?: number; delayBetweenBatches?: number } = {}
): Promise<R[]> {
const { batchSize = 50, delayBetweenBatches = 0 } = options;
const results: R[] = [];
// データをバッチに分割
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
// バッチを処理
const batchResults = await processFn(batch);
results.push(...batchResults);
// バッチ間に遅延を入れる(APIレート制限などに対応)
if (delayBetweenBatches > 0 && i + batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, delayBetweenBatches));
}
// 進捗状況を表示
const progress = Math.min(i + batchSize, items.length) / items.length * 100;
console.log(`処理進捗: ${progress.toFixed(1)}% (${i + batch.length}/${items.length})`);
}
return results;
}
// 使用例
async function updateAllUsers(users: User[]): Promise<UserUpdateResult[]> {
return processBatches(
users,
async (batch) => {
// 各バッチを並行して処理
const updatePromises = batch.map(user => updateUserData(user));
return Promise.all(updatePromises);
},
{ batchSize: 20, delayBetweenBatches: 500 }
);
}
Web Workersを使った並列処理
計算負荷の高い処理をメインスレッドでブロックせずに実行するには、Web Workersを活用できます。
// Web Workerを使った並列処理
function runInWorker<T, R>(
workerFunction: (data: T) => R,
data: T
): Promise<R> {
// Web Workerで実行する関数を文字列化
const fnString = workerFunction.toString();
// Web Workerのコード
const workerCode = `
self.onmessage = function(e) {
const fn = ${fnString};
const result = fn(e.data);
self.postMessage(result);
}
`;
// Blob URLを作成
const blob = new Blob([workerCode], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
return new Promise<R>((resolve, reject) => {
const worker = new Worker(url);
worker.onmessage = (e) => {
resolve(e.data);
worker.terminate();
URL.revokeObjectURL(url);
};
worker.onerror = (e) => {
reject(new Error(`Worker error: ${e.message}`));
worker.terminate();
URL.revokeObjectURL(url);
};
worker.postMessage(data);
});
}
// 使用例
async function processLargeDataset(data: LargeDataset): Promise<ProcessedResult> {
return runInWorker(
(input) => {
// 重い計算処理(メインスレッドをブロックしない)
const result = {
processed: input.items.map(item => heavyComputation(item)),
summary: calculateSummary(input.items)
};
return result;
},
data
);
}
Stream APIの活用
大きなデータを扱う場合、Stream APIを使用してメモリ効率を向上させることができます。
// Stream APIを使ったデータ処理
async function processLargeJSONStream(url: string): Promise<ProcessedData> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
// レスポンスをストリームとして取得
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let jsonChunks = '';
let processedItems = 0;
const results: ProcessedItem[] = [];
try {
// チャンクごとにデータを処理
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// UTF-8デコード
const chunk = decoder.decode(value, { stream: true });
jsonChunks += chunk;
// JSONの区切りを探す(この例では行区切りのJSONを想定)
const lines = jsonChunks.split('\n');
// 最後の不完全な行を除いて処理
jsonChunks = lines.pop() || '';
for (const line of lines) {
if (line.trim()) {
try {
const item = JSON.parse(line);
const processed = processItem(item);
results.push(processed);
processedItems++;
// 進捗報告
if (processedItems % 1000 === 0) {
console.log(`${processedItems}件処理完了`);
}
} catch (e) {
console.error('JSON解析エラー:', e);
}
}
}
}
// 残りのチャンクを処理
if (jsonChunks.trim()) {
try {
const item = JSON.parse(jsonChunks);
const processed = processItem(item);
results.push(processed);
} catch (e) {
console.error('最終チャンクのJSON解析エラー:', e);
}
}
return {
items: results,
totalProcessed: processedItems
};
} finally {
// リソースのクリーンアップ
reader.releaseLock();
}
}
Promise.anyの活用
複数の非同期処理のうち、最初に成功した結果だけが必要な場合はPromise.any()
が便利です。
// Promise.anyの例(最も速いAPIからデータを取得)
async function fetchFromFastestMirror<T>(urls: string[]): Promise<T> {
// 各URLからデータをフェッチするPromiseを作成
const promises = urls.map(async (url, index) => {
try {
const startTime = Date.now();
const response = await fetch(url);
if (!response.ok) {
throw new Error(`ミラー ${index + 1} エラー: ${response.status}`);
}
const data = await response.json();
const endTime = Date.now();
console.log(`ミラー ${index + 1} レスポンス時間: ${endTime - startTime}ms`);
return data as T;
} catch (error) {
console.error(`ミラー ${index + 1} 失敗:`, error);
throw error;
}
});
try {
// 最初に成功したPromiseの結果を返す
return await Promise.any(promises);
} catch (error) {
// すべてのPromiseが失敗した場合
if (error instanceof AggregateError) {
throw new Error(`すべてのミラーが失敗しました: ${error.errors.map(e => e.message).join(', ')}`);
}
throw error;
}
}
これらのテクニックを適切に組み合わせることで、TypeScriptアプリケーションの非同期処理を効率的かつパフォーマンスを意識して実装できます。並行処理の最適化は、特に大規模なデータ処理やリアルタイム機能を持つアプリケーションで重要になります。
おすすめコンテンツ
おすすめ生成AI2025/5/12生成AI開発者のためのエラーハンドリング完全ガイド:問題解決のベストプラクティス
生成AIアプリケーション開発で直面する一般的なエラーとその解決法を解説。プロンプトエンジニアリングからAPI連携、メモリ管理まで、実践的なコード例とトラブルシューティング手法を紹介します。これ一冊で生...
続きを読む IT技術2025/5/4TypeScriptのType Guardsで型安全なコードを書く方法:初心者向け完全ガイド
TypeScriptのType Guards(型ガード)は、コードの型安全性を高め、バグを減らすための強力な機能です。このガイドでは、TypeScriptの型ガードの基本から応用まで、実際のコード例を...
続きを読む React2025/5/1Next.jsとTypeScriptで作る高速SSGブログ:初心者でも簡単に実装できる完全ガイド
Next.jsとTypeScriptを組み合わせたSSGブログの作り方を解説します。静的サイト生成の利点から実装方法、デプロイまで、初心者でも理解できるようステップバイステップで説明。コードサンプル付...
続きを読む IT技術2025/5/1TypeScript開発を劇的に効率化する13のベストプラクティス
TypeScriptプロジェクトの開発効率を高めるベストプラクティスを紹介します。プロジェクト設定から型活用テクニック、コードの最適化まで、実務で即役立つ具体例とともに解説し、TypeScriptの真...
続きを読む GraphQL2025/5/4実践的GraphQLエラーハンドリング:効率的なAPIデバッグと堅牢なクライアント実装のガイド
GraphQLのエラーハンドリングに関する包括的なガイドです。サーバーサイドでのエラー設計から、クライアントサイドでの適切な処理まで、実践的なコード例と共に詳しく解説します。
続きを読むGo
2025/5/2Golangの並行処理パターン:効率的なアプリケーション開発のための実践ガイド
Golangのgoroutineとチャネルを使った並行処理パターンを実践的なコード例とともに解説。基本から応用まで、効率的な非同期処理の実装方法とよくあるエラーの回避策を紹介します。
続きを読む React2025/5/16TypeScriptでの型安全な状態管理:Zustandを使った実践的アプローチ
TypeScriptとZustandを組み合わせた型安全な状態管理の方法を学びましょう。シンプルでありながら強力な状態管理ライブラリZustandの基本から応用まで、実践的なコード例を交えて解説します...
続きを読む Python2025/5/9Pythonでエラーハンドリングを徹底マスター!実践的なテストケースによる学び方を解説
Pythonにおけるエラーハンドリングの基本から応用まで、実践的なテストケースを通じて学ぶ方法を解説します。例外処理の基本的な構文、よくあるエラーパターンとその対策、テストを活用したエラーハンドリング...
続きを読む