このカリキュラムでは、JavaScriptの非同期処理について深く掘り下げ、その設計パターンや実践的な使い方を学びます。
Promiseやfetch API
、WebSocket
などを駆使したリアルタイムアプリの開発や、エラーハンドリングと再試行ロジックまで、即戦力となるスキルを身につけることを目的としています。

1.非同期処理の応用
1.1 非同期処理の設計パターン
Promiseベースの設計
Promiseを使った設計では、複数の非同期処理を効率的に組み合わせることができます。
Promise.all() の活用
複数の非同期処理を並列実行し、全ての処理が成功した後に次の処理を実行します。
- 例:
const promise1 = new Promise((resolve) => setTimeout(() => resolve('タスク1完了'), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('タスク2完了'), 2000));
Promise.all([promise1, promise2]) .then((results) => console.log(results)) // ["タスク1完了", "タスク2完了"] .catch((error) => console.error(error));
- 用途:
- 複数のAPIリクエストを同時に処理する場合。
- 並列タスクの進行が求められる場面。
- 複数のAPIリクエストを同時に処理する場合。
Promise.race() の活用
複数の非同期処理のうち、最初に完了した処理の結果を使用します。
- 例:
const promise1 = new Promise((resolve) => setTimeout(() => resolve('タスク1完了'), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('タスク2完了'), 500));
Promise.race([promise1, promise2]) .then((result) => console.log(result)) // "タスク2完了" .catch((error) => console.error(error));
- 用途:
- 最速で応答を得たい場合(例: 予備のAPIリクエスト)。
1.2 エラーハンドリングの考え方
.catch() と try-catch の違いと使いどころ
.catch()
: Promiseチェーンの中でエラーをキャッチする。fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error('エラー発生:', error));
try-catch
: 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);
}
}
非同期処理におけるエラー伝播の仕組み
- Promiseチェーン内でエラーが発生すると、そのエラーは次の
.catch()
に伝播されます。 - async/awaitでは、
await
で発生したエラーはtry-catch
でキャッチ可能です。
2.Web APIとの連携
2.1 fetch APIの詳細
fetch APIとは?
fetch API
は、ブラウザ内でネットワークリクエストを行うためのJavaScriptのモダンなインターフェースです。
HTTPリクエストを送信し、サーバーからデータを取得したり、送信したりする用途で使われます。
fetch APIの特徴:
- Promiseベース:
非同期処理をPromise
として扱うため、可読性が高く、then
やcatch
で簡単に処理を記述できます。 - 柔軟な設計:
HTTPメソッド(GET, POST, PUT, DELETEなど)の指定や、リクエストヘッダー、ボディの柔軟な設定が可能。 - 標準対応:
モダンブラウザのほとんどでサポートされており、軽量です。
2.2 XMLHttpRequestとの違い
fetch API
と従来のXMLHttpRequest (XHR)
の違いを以下に比較します。
特徴 | fetch API | XMLHttpRequest |
---|---|---|
非同期処理 | Promiseベースで扱いやすい | コールバックが必要で複雑 |
API設計 | シンプルで直感的 | 冗長なコードになる |
Stream対応 | 対応 | 非対応 |
ブラウザサポート | モダンブラウザ向け | 古いブラウザでも対応 |
例えば、同じデータを取得するコードも、以下のようにfetch API
のほうが簡潔です。
fetch APIの例
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error('エラー:', error));
XMLHttpRequestの例
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = function () {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
} else {
console.error('エラー:', xhr.status);
}
};
xhr.onerror = function () {
console.error('ネットワークエラー');
};
xhr.send();
2.3 基本的なfetch APIの使い方
1. GETリクエスト: データ取得の例
GETリクエストはサーバーからデータを取得するために使用します。
javascriptコピーする編集するfetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return response.json();
})
.then((data) => console.log(data)) // 取得したデータを表示
.catch((error) => console.error('エラー:', error));
2. POSTリクエスト: サーバーへのデータ送信例
POSTリクエストは、サーバーにデータを送信するときに使用します。
javascriptコピーする編集するfetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'John', age: 30 }), // 送信するデータ
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return response.json();
})
.then((data) => console.log('送信成功:', data))
.catch((error) => console.error('エラー:', error));
3. ヘッダーの設定と認証トークンの利用
APIによっては、認証トークンをヘッダーに付与する必要があります。
javascriptコピーする編集するfetch('https://api.example.com/protected-data', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
},
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return response.json();
})
.then((data) => console.log(data))
.catch((error) => console.error('エラー:', error));
2.4 fetch APIのエラーハンドリング
1. statusコードによるエラー判定
HTTPレスポンスのstatus
コードをチェックして、エラーを判定します。
- 200系:
成功 - 400系:
クライアントエラー(例: 404 Not Found) - 500系:
サーバーエラー(例: 500 Internal Server Error)
fetch('https://api.example.com/data')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return response.json();
})
.catch((error) => console.error('エラー:', error));
2. ネットワークエラーとHTTPエラーの違い
- ネットワークエラー:
サーバーとの接続に失敗した場合に発生(例: サーバーダウンや接続タイムアウト)。 - HTTPエラー:
サーバーが返すHTTPステータスコードによって判断。
2.5 エラーハンドリングと再試行ロジック
再試行ロジックの実装
ネットワーク通信は、不安定な環境下でエラーが発生しやすいため、一定回数再試行する仕組みを取り入れることで、ユーザー体験を向上させることができます。
1. ネットワークエラーが発生した際のリトライ戦略
ネットワークエラーが発生した場合、指定回数だけリトライを試みます。
以下は、再試行ロジックの基本例です。
javascriptコピーする編集するasync function fetchWithRetry(url, options, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return await response.json();
} catch (error) {
if (i === retries - 1) throw error; // リトライ回数を超えたらエラーをスロー
console.warn(`リトライ中 (${i + 1}/${retries})...`);
}
}
}
2. 最大リトライ回数の設定例
retries
パラメータを指定して、最大リトライ回数をコントロールします。
fetchWithRetry('https://api.example.com/data', {}, 5) // 最大5回再試行
.then((data) => console.log(data))
.catch((error) => console.error('最終的に失敗:', error));
3. リトライ間隔の工夫(指数バックオフなど)
再試行時に、単純に再実行するのではなく、再試行間隔を増やしていく「指数バックオフ」を実装することで、サーバーへの負荷を軽減できます。
async function fetchWithExponentialBackoff(url, options, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return await response.json();
} catch (error) {
if (i === retries - 1) throw error;
const backoffDelay = delay * Math.pow(2, i); // 指数バックオフ
console.warn(`リトライ中 (${i + 1}/${retries}), ${backoffDelay}ms 待機`);
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
}
}
}
3.リアルタイムアプリ開発に向けた非同期処理
3.1 WebSocketの基本
WebSocketとは?
WebSocketは、ブラウザとサーバー間での双方向通信を可能にするプロトコルです。
従来のHTTPリクエスト/レスポンスモデルに比べ、低遅延で効率的なデータ通信が可能です。
- 特徴:
- 双方向通信が可能で、サーバー側からもデータを送信できる。
- 接続が一度確立されると、データ交換時にヘッダー情報を含む必要がないため、通信量が削減される。
- チャットアプリ、リアルタイム通知、ゲーム、ストリーミングなどで利用される。
- 双方向通信が可能で、サーバー側からもデータを送信できる。
HTTP通信との違い
項目 | HTTP | WebSocket |
---|---|---|
通信モデル | リクエスト-レスポンス | 双方向通信 |
接続の仕組み | 毎回新しい接続を確立する | 接続を1回確立し、それを維持する |
通信量 | リクエストごとにヘッダーを送信 | ヘッダーが不要、効率的な通信 |
ユースケース | APIリクエスト、静的データ取得 | チャット、通知、リアルタイム更新 |
双方向通信が可能な仕組み
WebSocketは、以下のように動作します。
- クライアント(ブラウザ)とサーバー間で初期的にHTTPリクエストを送信し、接続を確立します(ハンドシェイク)。
- 接続が確立されると、HTTPからWebSocketプロトコルに切り替わります。
- その後、接続が切れるまでサーバー・クライアント間で自由にメッセージを送受信できます。
WebSocketの基本的な使い方
サーバーとの接続、メッセージ送信、受信
以下は、基本的なWebSocketのコード例です。// WebSocket接続を確立
const socket = new WebSocket('wss://example.com/socket');
// 接続が確立したとき
socket.addEventListener('open', () => {
console.log('WebSocket connection opened');
// サーバーにメッセージを送信
socket.send('Hello Server!');
});
// サーバーからメッセージを受信したとき
socket.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
// 接続が閉じられたとき
socket.addEventListener('close', () => {
console.log('WebSocket connection closed');
});
// エラーが発生したとき
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
WebSocketのイベント
イベント名 | 説明 |
---|---|
open | 接続が確立された際に発生します。ここで初期メッセージをサーバーに送信できます。 |
message | サーバーからのメッセージを受信したときに発生します。 |
close | 接続が終了した際に発生します。サーバーまたはクライアント側で終了をトリガー可能です。 |
error | 接続中にエラーが発生した際に発生します。エラーの詳細を確認し、再接続を試みるきっかけになります。 |
3.2 WebSocketの応用
非同期処理を活用したリアルタイム通信
WebSocketを利用すると、非同期処理を活用して効率的なリアルタイム通信を実現できます。
例えば、複数の非同期メッセージを送信・受信する際のキュー管理や、接続が切れた際の再接続ロジックが重要なポイントになります。
メッセージのキュー管理
リアルタイム通信では、通信が不安定な場合や接続が一時的に切れた場合に備え、メッセージを一時的に保存(キューイング)する仕組みが役立ちます。
キュー管理の例:
以下は、送信メッセージをキューに格納し、接続が復旧した際に再送信する例です。
let messageQueue = []; // 送信キュー
let isConnected = false; // 接続状態を追跡
const socket = new WebSocket('wss://example.com/socket');
// 接続が確立した際に、キューのメッセージを送信
socket.addEventListener('open', () => {
isConnected = true;
console.log('WebSocket connection established');
// キューに溜まったメッセージを送信
while (messageQueue.length > 0) {
const message = messageQueue.shift(); // キューから取得
socket.send(message);
}
});
// メッセージを送信する関数
function sendMessage(message) {
if (isConnected) {
socket.send(message);
} else {
console.log('Connection lost. Queuing message:', message);
messageQueue.push(message); // キューに追加
}
}
通信の再接続ロジック
WebSocketの接続が切れることはよくあるため、自動的に再接続する仕組みを設計する必要があります。
再接続ロジックの例:
指数バックオフを用いて、再接続の待機時間を段階的に増やす戦略を取り入れます。
let retryAttempts = 0; // 再接続試行回数
const maxRetries = 5; // 最大再接続回数
let socket;
function connectWebSocket() {
socket = new WebSocket('wss://example.com/socket');
// 接続が確立されたとき
socket.addEventListener('open', () => {
console.log('WebSocket connection established');
retryAttempts = 0; // 再接続カウンターをリセット
});
// 接続が閉じられたとき
socket.addEventListener('close', () => {
console.log('WebSocket connection closed');
if (retryAttempts < maxRetries) {
const retryDelay = Math.min(1000 * 2 retryAttempts, 30000); // 指数バックオフ
retryAttempts++;
setTimeout(connectWebSocket, retryDelay);
} else {
console.error('Max retries reached. Could not reconnect.');
}
});
// エラーが発生したとき
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
}
// 初回接続
connectWebSocket();
リアルタイム通信のユースケース
- チャットアプリ:
WebSocketを用いたリアルタイムメッセージングアプリ。 - 通知システム:
サーバーからのリアルタイム通知(例: 新しいメッセージやアラート)。 - データストリーミング:
株価、ライブスコア、SNSフィードの更新。
まとめ
リアルタイムアプリ開発では、非同期処理を活用した効率的なデータ通信が不可欠です。
本記事では、WebSocketの基本から応用までを解説し、双方向通信の仕組みや、接続維持のためのキュー管理、再接続ロジックについて学びました。
重要なポイントを振り返ると…
- WebSocketの基本
WebSocketは、双方向通信を可能にするプロトコルであり、HTTP通信よりも効率的なデータ交換を実現します。
基本的なイベント (open
,message
,close
,error
) を理解することで、リアルタイム通信の基盤を構築できます。 - 非同期処理の応用
メッセージのキュー管理により、接続が不安定な場合でもデータを損失せず、再接続ロジックを活用して安定した通信を確保できます。 - 応用例とユースケース
チャットアプリ、通知システム、ストリーミングデータの表示など、現実のユースケースを想定した設計が可能になります。