16非同期処理
この章では非同期処理に関して解説していきます。
まずはそれらに関連するスレッド・通常の処理(同期処理)について説明します。
スレッドについての基本
スレッドとは、コンピュータプログラムがタスクを実行する際の実行流(プログラムの開始から終了までの一連の処理)の単位です。
一般に、プログラムはスタートからエンドまでの指示(コード)に沿って実行されます。この一連の指示を実行するのが「メインスレッド」です。
プログラムが単一のメインスレッドのみを使用する場合、これをシングルスレッド実行環境と呼びます。
通常の処理(同期処理)
JavaScriptのようなシングルスレッド環境では、コードは上から下へと順番に実行されます。
一つの処理が始まると、その処理が完了するまで次の処理は待たされます。
このような処理の流れを「同期処理」と呼びます。同期処理では、ある処理の完了を必ず待ってから次の処理に移ります。
非同期処理の導入
しかし、すべての操作がすぐに終わるわけではありません。
データの読み込みや外部APIへのリクエストなど、時間がかかる処理が存在します。これらの処理をメインスレッドで同期的に行うと、処理が完了するまでアプリケーションが応答しなくなります。
これを避けるために、「非同期処理」が用いられます。非同期処理を利用すると、時間がかかるタスクをメインスレッドから切り離し、その完了を待たずに次の処理に進むことができます。
タスクが完了すると、コールバック関数やPromise、async/awaitを通じて結果を処理できます。
16-1 コールバック関数
コールバック関数は、他の関数に引数として渡される関数です。渡された関数(コールバック関数)は、外側の関数(親関数)の内部で、特定のタイミングで実行されます。
この特性を利用して、JavaScriptでは非同期処理の完了時に特定の動作を実行するためにコールバック関数を使用します。
JavaScriptの実行環境では、メインスレッドがシングルスレッドであるため、時間のかかる操作(例えば、ネットワークリクエストや大きなファイルの読み込み)を非同期に処理することが推奨されます。
これにより、その操作が完了するのを待っている間も、アプリケーションは他のタスクを実行し続けることができます。
コールバック関数は、このような非同期処理の完了時に呼び出されることで、非同期操作の結果に基づいた処理を実行するための機構を提供します。
例えば、Web APIを使用してサーバーからデータをフェッチする際(データをサーバーから取得するとき)、データの取得が完了したら特定の処理を行うためにコールバック関数を使用します。
以下の例では、showMessageAfterTimeout関数が「メッセージを表示する」という簡単な「データ取得」のような操作を模擬しています。
この関数は2つの引数を取ります:表示したいメッセージと、メッセージが表示された後に実行したい処理を定義したコールバック関数です。関数を実行すると、まず3秒間待ちます。
その後、指定されたメッセージ(ここでは「こんにちは、世界!」)がコンソールに表示され、その直後にコールバック関数が呼び出されて、「メッセージが表示されました」というメッセージがコンソールに表示されます。
例:
function showMessageAfterTimeout(message, callback) {
setTimeout(() => {
// 3秒後にこの関数内のコードが実行されます
console.log(message); // コンソールにメッセージを表示
callback(); // コールバック関数を呼び出す
}, 3000); // 3000ミリ秒 = 3秒
}
showMessageAfterTimeout('こんにちは、世界!', function() {
//無名関数
console.log('メッセージが表示されました'); // メッセージ表示後に実行される
});この例では、showMessageAfterTimeout関数が「メッセージを表示する」という簡単な「データ取得」のような操作を模擬しています。この関数は2つの引数を取ります:表示したいメッセージと、メッセージが表示された後に実行したい処理を定義したコールバック関数です。
関数を実行すると、まず3秒間待ちます。その後、指定されたメッセージ(ここでは「こんにちは、世界!」)がコンソールに表示され、その直後にコールバック関数が呼び出されて、「メッセージが表示されました」というメッセージがコンソールに表示されます。
16-2 Promise
PromiseはJavaScriptの強力なツールで、将来的に発生する値を扱いながら非同期処理を簡潔に記述できます。
これにより、非同期操作の成功結果や失敗理由を明確に表現し、コードのネストが深くなることなく可読性を高めることが可能になります。
Promiseは以下の3つの状態のいずれかを持ちます。
- Pending(保留中):非同期操作がまだ完了していない状態。
- Fulfilled(履行済み):非同期操作が成功し、Promiseが値を持っている状態。
- Rejected(拒否):非同期操作が失敗した状態。
Promiseを使う基本的な方法は、new PromiseでPromiseオブジェクトを作成し、その中で非同期処理を行うことです。
非同期処理が成功したらresolveを呼び出し、失敗したらrejectを呼び出します。
以下の例では、new Promiseで新しいPromiseオブジェクトを作成し、その処理が成功するかどうかに基づいてresolveかrejectを呼び出しています。
その後、.thenメソッドで成功時の処理を、.catchメソッドで失敗時の処理を定義しています。
例:
let myPromise = new Promise(function(resolve, reject) {
// 何かの非同期操作
let isSuccessful = true; // 例として、操作が成功したとします
if (isSuccessful) {
resolve('操作が成功しました');
} else {
reject('操作が失敗しました');
}
});
myPromise.then(function(value) {
console.log(value); // 成功した場合に実行される。出力: 操作が成功しました
}).catch(function(error) {
console.log(error); // 失敗した場合に実行される。出力: 操作が失敗しました
});また、.finallyメソッドを使用して非同期処理が完了した後に必ず行いたい処理を定義することもできます。
.finallyは成功でも失敗でも関係なく、Promiseの処理が終わった後に実行される処理を設定するのに便利です。
これは、例えばリソースのクリーンアップなど、成功失敗に関わらず共通して行いたい後処理がある場合に特に有効です。
例:
myPromise
.then(function(value) {
console.log(value);
})
.catch(function(error) {
console.log(error);
})
.finally(function() {
console.log('非同期処理が完了しました');
});このように、Promiseとそのメソッド(.then, .catch, .finally)を使うことで、非同期処理の結果に基づいて処理を分岐させたり、処理が完了した後のクリーンアップ作業を行ったりすることができます。
次に実践的な例を見てみましょう。
また以下のコードは、Promiseを用いた疑似的にWeb APIからデータを取得するシナリオを模倣したものです。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 擬似的なデータ取得処理
let data = "Web APIから取得したデータ";
resolve(data); // 成功した場合
}catch (error) {
reject("データ取得中にエラーが発生しました"); // 失敗した場合
}
}, 2000); // 2秒後に結果を返す
});
}
fetchData().then((data) => {
console.log(data); // 成功した場合の処理
}).catch((error) => {
console.error(error); // 失敗した場合の処理
});fetchData関数では、new Promiseを用いてPromiseオブジェクトを生成し、非同期処理の成功時にresolveを、失敗時にrejectを呼び出すことで、非同期操作の結果を扱っています。
この関数内でsetTimeoutを使用し、2秒後に擬似的なデータ取得を完了させる非同期処理を模倣しています。
非同期操作が成功した場合、.then()メソッドで取得データを処理し、失敗した場合には.catch()メソッドでエラーハンドリングを行います。
Promiseを活用することにより、非同期処理を直感的かつ効率的に管理でき、複雑な処理フローも柔軟に扱えるため、Web開発において重要な技術となっています。
16-3 Async/Await
JavaScriptのasync/await構文は、非同期処理をより簡潔で読みやすく記述するための機能です。この機能は、Promiseベースの非同期処理を、まるで同期処理のように直感的に扱うことができるようにします。
asyncとawaitを使うことで、コールバック関数や.then()チェーンを使うことなく、非同期処理の流れを簡単に追跡できます。
async関数
- asyncを関数の前に置くことで、その関数はasync関数になります。
- async関数は、常にPromiseを返します。関数内でreturnされた値は、Promiseによって解決される値となります。
- 関数内で発生した例外は、Promiseによって拒否される理由となります。
await演算子
- await演算子は、async関数内でのみ使用できます。
- awaitは、Promiseが履行(fulfill)または拒否(reject)されるのを待ちます。
- Promiseが履行されると、その解決値を返します。拒否されると、例外を投げて処理を中断します。
async/awaitの例
以下の例は、async/awaitを使用して非同期にデータを取得する簡単な関数を示しています。
// 非同期にデータを取得するダミー関数
function fetchData() {
return new Promise(resolve => setTimeout(() => resolve("データ取得完了!"), 2000));
}
// async/awaitを使った非同期処理
async function getData() {
console.log("データ取得を開始します...");
const data = await fetchData(); // fetchDataが解決されるまで待つ
console.log(data); // "データ取得完了!"
}
getData();この例では、fetchData関数がPromiseを返し、2秒後に解決します。getData関数内では、awaitを使ってfetchDataの完了を待ち、その結果をdata変数に格納しています。fetchDataからの解決値(ここでは”データ取得完了!”)は、awaitの後の行で利用可能になります。
async/await構文により、非同期コードを書く際の複雑さが大幅に減少します。これにより、非同期処理を含むコードが、同期処理を使用する場合と同じくらい簡単に理解しやすくなります。
async/awaitを使い始めることで、JavaScriptにおける非同期処理の管理がずっと扱いやすくなリます。
次に実践的な例を見てみましょう。
非同期処理を行う実践的なサンプルコードとして、外部APIからデータを取得して表示する関数をfetchAPIとasync/awaitを使用して実装します。
この例では、公開されている無料のAPIを使って、特定の情報(例えば、JSONPlaceholderからの投稿)を取得します。
以下のコードは、JSONPlaceholderの偽の投稿データを取得してコンソールに表示するシンプルな例です。
JSONPlaceholderはテスト用のREST APIを提供しており、非同期処理の練習に最適です。
async function fetchPosts() {
try {
// JSONPlaceholderから投稿データを取得
let response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (!response.ok) {
throw new Error('データの取得に失敗しました。');
}
let post = await response.json(); // レスポンスのボディをJSONとして解析
console.log(post);
} catch (error) {
// エラーハンドリング
console.error('エラーが発生しました:', error);
}
}
// 関数を呼び出して投稿を取得
fetchPosts();
コメントを残す