JavaScript 基礎 第14回 では、Web APIと JavaScript の関係について学んで頂きました。
今回は JavaScript の非同期処理を使用することによって、ユーザーの操作を妨げずにAPIからデータを取得したり、他の非同期操作を行うことを学習していきましょう!
JavaScript 同期処理、非同期処理
非同期処理
JavaScript の非同期処理では、あるタスクを実行している際に、その処理を止めることなく別のタスクを実行できる。
メリット
- アプリケーションがフリーズすることなく、バックグラウンドで重い処理を行えるため、ユーザーの操作性が向上する。
- 長時間かかるI/O操作を待っている間も他のタスクを実行できるため、CPUや他の資源を効率的に利用できる。
デメリット
- コードが複雑になりやすく、特に※コールバック地獄や非同期のエラーハンドリングが難しい場合がある。
- 非同期処理のエラーやバグは、同期処理と比べて特定しづらく、トラブルシューティングに時間がかかることがある。
※コールバック
コールバックは、JavaScriptの非同期プログラミングで非常に重要な概念です。これにより、非同期操作が完了した後に実行される処理を簡潔に定義できます。しかし、コールバックが多重にネストすると、コールバック地獄(callback hell)と呼ばれる問題が発生しやすくなります。これを解決するために、この後に出てくるPromise
やasync/await
といった他の手法も広く使われるようになっています。
■コールバック地獄の例
function firstOperation(callback) {
setTimeout(() => {
console.log("最初の操作が完了しました");
callback();
}, 1000);
}
function secondOperation(callback) {
setTimeout(() => {
console.log("2番目の操作が完了しました");
callback();
}, 1000);
}
function thirdOperation(callback) {
setTimeout(() => {
console.log("3番目の操作が完了しました");
callback();
}, 1000);
}
// コールバック地獄の例
firstOperation(() => {
console.log("最初の操作が完了し、2番目の操作を開始します");
secondOperation(() => {
console.log("2番目の操作が完了し、3番目の操作を開始します");
thirdOperation(() => {
console.log("すべての操作が完了しました");
});
});
});
同期処理
同期処理では、コードは上から下へ順番に実行されます。複数の処理を実行する際にひとつの処理が完了するまで次の処理は始まりません。
メリット
- 順番でタスクが処理されるので、処理全体を把握しやすい。
デメリット
- タスクの処理の終了までに時間がかかることや、別の処理が実行できないことで、ユーザー視点ではストレスとなる場合がある。
よく使われる JavaScript の非同期処理
Promise
Promiseは、非同期処理の結果を表すオブジェクトで、3つの状態を持ちます。
- pending(通信中):初期状態。処理がまだ完了していない。
- fulfilled(通信完了):処理が成功した。
- rejected(通信拒否):処理が失敗した。
Promiseは、処理の順序付けをすることができます。処理を待機することや、その結果に応じて次の処理をすることが可能です。
■例
console.log("1. 最初の処理完了");
// 3秒後に実行する処理
setTimeout(() => {
console.log("2. 第2の処理完了");
}, 3000);
console.log("3. 第3の処理完了");
上記処理を実行すると「第2の処理」が3秒後に実行されるので、先に「第3の処理」が完了します。
上記を想定通りの順番に実行したい場合にPromiseを使用します。
console.log("1. 最初の処理完了");
const promise = new Promise((resolve) => {
// 3秒後に実行する処理
setTimeout(() => {
console.log("2. 第2の処理完了");
resolve();
}, 3000);
});
promise.then(() => {
console.log("3. 第3の処理完了");
});
上記コードに書き換えることで、Promiseオブジェクトと.then()により順番を決めることができました。「最初の処理」が終わり、次に「第2の処理」がログに表示されるのを3秒待ってから、最後に「第3の処理」が表示されました。
制御しない場合ではJavaScriptは非同期で時間のかからない処理から実行していましたね。
Ajax (Asynchronous JavaScript and XML)
Ajaxは、非同期的にサーバーと通信するための手法で、XMLHttpRequest
オブジェクトを使用します。これにより、ページ全体を再読み込みすることなく、サーバーからデータを取得したり送信したりすることができます。
メリット
- 多くのモダンブラウザと多くの古いブラウザでサポートされている。
- jQueryを使用することで簡潔な記述が可能。
デメリット
XMLHttpRequest
のAPIは比較的冗長で、読み書きが複雑になりやすいです。- Promiseベースではないため、コールバックが多用され、可読性が低下することがあります。
■例
console.log('APIリクエストを送信します');
// XMLHttpRequestオブジェクトを作成
var xhr = new XMLHttpRequest();
// リクエストの種類とエンドポイントを指定(非同期で処理する)
xhr.open('GET', 'http://localhost:3000/tokyo', true);
// リクエストの状態が変化したときに実行する処理を定義
xhr.onreadystatechange = function() {
// リクエストの状態が「DONE」(4)のときに処理を実行
if (xhr.readyState === 4) {
// リクエストが成功した場合
if (xhr.status === 200) {
// レスポンスデータをJSON形式からJavaScriptオブジェクトに変換
var data = JSON.parse(xhr.responseText);
console.log('データを取得しました:', data);
} else {
// リクエストが失敗した場合はエラーメッセージをコンソールに表示
console.error('エラーが発生しました:', xhr.statusText);
}
}
};
// リクエストを送信
xhr.send();
console.log('他の処理を実行します');
今回は例のコードのデータの取得にはデータとサーバーの起動が必要になるので、コードを見て頂くのみで大丈夫です。次の回で実際に簡単な実践をするので、その際に非同期処理を書いてみましょう!
上記のコードではデータを取得しながら他の処理が動いている状態です。
記述されたコードが何をしているのかはコードについているコメントを読んでみてください。
いきなり複雑な記述になってきたので、すぐに理解が出来なくても慌てないでください!
jQueryを使用して記述した場合
console.log('APIリクエストを送信します');
// jQueryを使用してGETリクエストを送信し、データを取得する
$.get('http://localhost:3000/tokyo')
// リクエスト成功時の処理
.done(function(data) {
console.log('データを取得しました:', data);
})
// リクエスト失敗時の処理
.fail(function(error) {
console.error('エラーが発生しました:', error.statusText);
});
console.log('他の処理を実行します');
- doneメソッド:通信に成功した場合。
- failメソッド:通信に失敗した場合
Fetch
Fetch APIは、XMLHttpRequest
の代替手段で、Promiseベースでデータが扱いやすく、内蔵ライブラリということで便利で、簡潔に非同期のHTTPリクエストを作成できます。
また、Fetch APIは、ブラウザ環境とNode.jsの両方で使用することができます。
メリット
- Promiseベースで、よりシンプルで読みやすいコードが書ける。
- 内蔵ライブラリなので、アップデートによるエラーを防ぐことができる。
then
やcatch
を使ったチェーンが可能で、非同期処理の管理が容易になる。
デメリット
- 新しいブラウザでサポートされていますが、非常に古いブラウザではサポートされていない場合がある。
- HTTPエラー(ステータスコードが200以外)の場合でもPromiseがfulfilledになるため、手動でエラーチェックが必要です。
■例
// APIリクエストを送信する前にメッセージをコンソールに表示
console.log('APIリクエストを送信します');
// fetchを使用してHTTP GETリクエストを送信し、レスポンスを処理するPromiseチェーンを開始
fetch('http://localhost:3000/tokyo', { method: 'GET' })
// レスポンスのJSONデータを取得し、次の処理に渡すPromiseを返す
.then(response => response.json())
// JSONデータの解析が成功した場合の処理
.then(data => {
// データをコンソールに表示
console.log('データを取得しました:', data);
})
// エラーが発生した場合の処理
.catch(error => {
// エラーメッセージをコンソールに表示
console.error('エラーが発生しました:', error);
});
// 他の処理の実行のメッセージをコンソールに表示
console.log('他の処理を実行します');
Ajaxで記載した処理と同じものをFetchを利用して記述しています。
基本の記述
- 基本的な使い方
- Fetch APIは、
fetch()
関数を使用してHTTPリクエストを行います。この関数は、リクエストを送信し、Promiseを返します。このPromiseは、サーバーからのレスポンスを表します。
- Fetch APIは、
- HTTPリクエストの作成
fetch()
関数の第1引数には、リクエスト先のURLを指定します。また、オプションの第2引数には、リクエストの詳細を指定するオブジェクトを渡します。例えば、リクエストのメソッド(GET、POSTなど)、ヘッダー、ボディなどを指定できます。
- Promiseを使用したレスポンス処理
fetch()
関数はPromiseを返すため、.then()
や.catch()
などのPromiseのメソッドを使用して、レスポンスを処理します。.then()
メソッドはリクエストが成功した場合に呼び出され、レスポンスオブジェクトが渡されます。.catch()
メソッドはリクエストが失敗した場合に呼び出され、エラーオブジェクトが渡されます。
- レスポンスの処理
- レスポンスオブジェクトには、レスポンスのステータス、ヘッダー、ボディなどが含まれます。
.json()
メソッドを使用して、レスポンスのJSON形式のボディを解析することができます。
- レスポンスオブジェクトには、レスポンスのステータス、ヘッダー、ボディなどが含まれます。
axios
axiosは、ブラウザとNode.jsの両方で動作するPromiseベースのHTTPクライアントです。axiosを使用することで、簡単にHTTPリクエストを行い、レスポンスを処理することができます。
メリット
- Fetchと同様にPromiseベースで、非同期処理の管理が容易。
- リクエストやレスポンスのインターセプター、タイムアウト設定、デフォルトのヘッダー設定など、便利な機能が多数用意されている。
- レスポンスを自動的にJSONに変換する機能があり、手動のパースが不要。
デメリット
- ネイティブAPIではないため、外部ライブラリとしてインストールが必要。
■例
console.log('APIリクエストを送信します');
axios.get('http://localhost:3000/tokyo')
.then(response => {
console.log('データを取得しました:', response.data);
})
.catch(error => {
console.error('エラーが発生しました:', error);
});
console.log('他の処理を実行します');
Ajax、Fetchで記載した処理と同じものをaxiosを利用して記述しています。
基本の記述
- axiosを使用するには、まずプロジェクトにAxiosをインストールする必要がある。
- axiosは、GET、POST、PUT、DELETEなどのHTTPリクエストを簡単に行うことができます。axiosの各メソッドはPromiseを返すため、
.then()
や.catch()
を使用して非同期処理を扱う。 - POSTリクエストを行う場合、
axios.post()
メソッドを使用します。また、データをリクエストに含めることもできます。
■POSTリクエストを行う際の例
axios.post('http://example.com/api/data', { key: 'value' })
.then(response => {
console.log('レスポンスデータ:', response.data);
})
.catch(error => {
console.error('エラーが発生しました:', error);
});
async/await
async/awaitは、非同期処理を扱うためのJavaScriptの構文で、Promiseよりも分かりやすく、シンプルな書き方にすることができます。
記述の仕方
async関数の定義
非同期処理を含む関数を定義する場合、async
を関数の前に付けます。
async function myFunction() {
// 非同期の処理
}
awaitの使用
await
は、Promiseが解決されるまで非同期処理の実行を一時停止します。通常、Promiseを返す関数呼び出しの前にawait
を置きます。
async function myFunction() {
let result = await someAsyncFunction();
console.log(result);
}
■例(Promise)
console.log("1. 最初の処理完了");
const promise = new Promise((resolve) => {
// 3秒後に実行する処理
setTimeout(() => {
console.log("2. 第2の処理完了");
resolve("Promiseの処理が完了しました");
}, 3000);
});
promise.then((val) => {
console.log(val);
console.log("3. 第3の処理完了");
});
promise.then(() => {
console.log("4. 第4の処理完了");
});
promise.then(() => {
console.log("5. 第5の処理完了");
});
■例(async/await)
async function myFunction() {
console.log("1. 最初の処理完了");
// 非同期処理を待つ
await new Promise((resolve) => {
// 3秒後に実行する処理
setTimeout(() => {
console.log("2. 第2の処理完了");
resolve("Promiseの処理が完了しました");
}, 3000);
});
console.log("3. 第3の処理完了");
console.log("4. 第4の処理完了");
console.log("5. 第5の処理完了");
}
// 非同期関数を呼び出す
myFunction();
async/awaitを使用した書き方にすると分かりやすく、簡潔に記述することができました。
関数内でawait
を使用することで、非同期処理の逐次実行やエラーハンドリングが容易になります。
まとめ
JavaScriptで非同期処理を行うことは、パフォーマンス、効率性、スケーラビリティ、およびユーザーエクスペリエンスの向上につながる重要な手段であり、現代のWeb開発において欠かせない要素となっています。
難しい回になりましたが、非同期処理とはどのようなものか、どのような記述をしているのかということに触れて、できることを知って頂ける機会になりましたか?
使用する際にまたこの記事に戻ってきて基礎を振り返るように利用して頂けると幸いです。
コメント