Contents
JavaScript 非同期 処理 実装方法を初心者向けに解説|async/awaitとPromiseの比較
JavaScriptにおける非同期処理は、Web開発において必須となる技術です。特にAPI通信やタイマーアクションなど、実行時間がかかる処理を扱う際には、非同期処理の理解がスムーズな開発に直結します。本記事では、初心者向けにasync/awaitとPromiseベースの実装例を比較しながら解説し、実習で活用できるコードサンプルも紹介します。
JavaScriptの非同期処理とは何か
JavaScriptは単一スレッドで動作する言語であり、同期処理では後続のコードが待機してしまうという課題があります。一方、非同期処理を用いることで、処理中の待機時間をブラウザに返還し、UIの操作性向上や効率的なリソース管理が可能になります。
同期処理と非同期処理の違い
同期処理ではコードが順番に実行され、後続の処理は前処理の完了を待たなければなりません。一方で、非同期処理はタスクを「今すぐ実行」ではなく「いつか実行」としてキューに入れ、他の処理と並列して進めることが可能です。
例として、fetch() APIを使用したデータ取得を考えてみましょう。
|
1 2 3 4 |
// 同期処理(待機してしまう) const data = fetch('https://api.example.com/data'); console.log(data); // まだデータが返ってこないのでundefined |
|
1 2 3 4 5 |
// 非同期処理(待機しない) fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)); |
イベントループの仕組み
JavaScriptはイベントループという機構によって非同期処理を実現しています。イベントループでは、メインスレッドに割り込みが発生すると、タスクをキューイングし、適切なタイミングで処理します。この仕組みにより、UIの応答性を維持しながらバックグラウンドでの処理が可能になります。
コールバック地獄の問題と解決方法
非同期処理を扱う際、コールバック関数をネストさせた構文(コールバック地獄)は可読性低下やバグの原因となる典型的な課題です。
ネストされたコールバックの課題
複数の非同期処理を連続して実行する場合、以下のようなコードが生まれます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fetch('https://api.example.com/data1') .then(response => { return response.json(); }) .then(data1 => { fetch(`https://api.example.com/data2?id=${data1.id}`) .then(response => { return response.json(); }) .then(data2 => { console.log('データ取得完了:', data1, data2); }); }); |
このコードは「コールバック地獄」と呼ばれ、処理の流れが視覚的に複雑になり、保守性が低下します。
Promiseの導入による改善
Promiseオブジェクトを使うことで、非同期処理を連鎖させながら明確なコード構造を保つことが可能になります。
|
1 2 3 4 5 6 7 8 |
fetch('https://api.example.com/data1') .then(response => response.json()) .then(data1 => { return fetch(`https://api.example.com/data2?id=${data1.id}`); }) .then(response => response.json()) .then(data2 => console.log('データ取得完了:', data1, data2)); |
このように、then()メソッドを連鎖させることで、処理の流れが直感的に理解しやすくなります。
Promiseオブジェクトの基本的な使い方
Promiseオブジェクトは非同期操作の結果(成功または失敗)を扱うためのオブジェクトです。以下にその基本的な使い方を解説します。
new Promise()によるオブジェクト生成
new Promise()を使用して、非同期処理を定義できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
const promise = new Promise((resolve, reject) => { // 非同期処理(例:タイマーやAPI呼び出し) setTimeout(() => { const success = true; if (success) { resolve('処理成功'); } else { reject('エラー発生'); } }, 1000); }); |
resolve():処理が成功した際に呼び出す関数reject():処理に失敗した際に呼び出す関数
then()とcatch()による結果の処理
then()メソッドで成功時の処理、catch()で失敗時の処理を定義します。
|
1 2 3 4 5 6 7 8 |
promise .then(result => { console.log('成功:', result); }) .catch(error => { console.error('エラー:', error); }); |
重要ポイント:
Promiseチェーンでは、then()の戻り値が次の処理に自動的に渡されるため、処理を連続させやすくする設計です。
Promiseチェーンによる複数処理の実行
then()メソッドは連鎖して使用でき、処理の流れを明確にします。
|
1 2 3 4 5 6 7 8 |
fetch('https://api.example.com/data1') .then(response => response.json()) .then(data1 => { return fetch(`https://api.example.com/data2?id=${data1.id}`); }) .then(response => response.json()) .then(data2 => console.log('データ取得完了:', data1, data2)); |
async/await構文の実装手順
async/awaitは、非同期処理を同期的なコードのように記述できる仕組みです。Promiseベースのコードから移行することで、可読性が大幅に向上します。
async関数の定義と使用
|
1 2 3 4 5 6 7 8 9 10 |
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); } } |
async:関数を非同期処理用に宣言await:非同期処理の完了を待機
Promiseベースとasync/awaitの比較
以下は両者の違いを明確化した表です。
| 項目 | Promiseベース | async/await |
|---|---|---|
| 可読性 | ネストが深くなりやすい | 直感的な記述ができる |
| エラーハンドリング | catch()で処理する |
try...catch構文で統一 |
| 実装スタイル | 階層構造の連鎖 | 同期処理に近い構文 |
setTimeoutやfetch APIとの併用例
非同期処理は、タイマーやネットワークリクエストなどと組み合わせて使用されることが多いため、具体的な実装例を紹介します。
タイマー関数とPromiseの併用
setTimeout()を非同期処理として扱うには、Promise化が必要です。
|
1 2 3 4 5 6 7 8 9 10 11 |
function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('開始'); await wait(2000); // 2秒待機 console.log('完了'); } run(); |
fetch APIとasync/awaitの併用
fetch() APIでデータ取得を行う非同期処理例です。
|
1 2 3 4 5 6 7 8 9 10 11 |
async function getData() { try { const response = await fetch('https://api.example.com/data'); if (!response.ok) throw new Error('ネットワークエラー'); const data = await response.json(); console.log(data); } catch (error) { console.error('エラー:', error.message); } } |
実習環境で試してみよう
本記事の内容を実際に試すため、以下にコピー可能なコードサンプルを用意しました。ブラウザ上で動作確認してください。
コピー可能なコードサンプル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Promiseベースの例 function fetchDataPromise() { return fetch('https://jsonplaceholder.typicode.com/posts/1') .then(response => response.json()) .catch(error => console.error('エラー:', error)); } // async/awaitベースの例 async function fetchDataAsync() { try { const response = await fetch('https://jsonplaceholder.typicode.com/posts/1'); if (!response.ok) throw new Error('ネットワークエラー'); const data = await response.json(); console.log(data); } catch (error) { console.error('エラー:', error.message); } } // 実行 fetchDataPromise(); fetchDataAsync(); |
ブラウザでの動作確認手順
- 上記のコードをテキストエディタでコピーします。
index.htmlファイルとして保存し、スクリプトタグで読み込みます。- ブラウザを開き、ファイルをロードしてコンソールに表示される結果を確認してください。
まとめ
- 非同期処理はUIのレスポンシブ性向上に不可欠
Promiseとasync/awaitは、コールバック地獄を解決する手法- 実際のコードでは
fetch()やsetTimeout()などとの併用が一般的 - async/awaitは可読性・保守性において優れた選択肢
記事で得られる知識まとめ
- JavaScriptの非同期処理とは何かを理解する
- コールバック地獄の問題とPromiseによる改善方法を学ぶ
- Promiseオブジェクトの基本的な使い方(
new Promise()、then()/catch()) - async/await構文での実装手順と同期処理に近い記述スタイルの利点
setTimeout()やfetch()APIとの併用例を比較して確認する
これらの知識は、実際の開発現場で即戦力となるスキルです。是非、コードサンプルを活用して理解を深めてください。