こんにちは!
プロクラスの国府です。
JavaScriptの非同期処理のプログラミングは、得意でしょうか?
私も含めて苦手な人が多そうなので、今回は復習がてらに本記事のテーマにしたいと思います。
まず、非同期処理と同期処理の違いについて
同期処理の場合
1.処理A
2.処理B
3.処理C
上から順番に処理していきます。
非同期処理の場合(通信、ファイルアクセス、イベントリスナーなど)
例えば、処理Bがサーバと通信する場合
1.処理A
2.処理Bの通信開始処理
3.処理C
4.処理Bの通信終了処理
の順番で処理されます。「2.処理Bの通信開始処理」で通信結果がもどるまで時間がかかるので、その間に「3.処理C」が実行されて、通信結果がでたら、「4.処理Bの通信終了処理」が処理されます。
Javascript における代表的な非同期処理手法である
- コールバック
- Promise
- async/await
の違いや使い方を整理していきたい思います。
- コールバックとは
非同期関数に関数を渡し、処理完了時にその関数を呼び出す手法。
setTimeout(() => {
console.log("3秒後に実行");
}, 3000);
setTimeout関数は、何ms後に指定された処理を実行するビルトイン関数(タイマー)。
第一引数は、指定された時間後に処理する関数(例はアロー関数)
第二引数は、処理を待機する時間(ミリ秒) (例は3000ミリ秒=3秒)
戻り値は、作成したタイマーの識別子
利点:処理が簡単、ネイティブ対応。
欠点: ネストが深くなりやすい(いわゆる「コールバック地獄」)、
エラー処理が分散し、見通しが悪くなる。
- Promise とは
将来完了するかもしれない処理(成功または失敗)の結果を扱うための仕組み
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (true) {
resolve("データ取得成功");
} else {
reject("データ取得失敗");
}
}, 1000);
});
}
fetchData()
.then(data => console.log(data))
.catch(err => console.error(err));
Promiseオブジェクトは、以下の3つの状態を持ちます
状態 | 説明 |
pending (保留) |
初期状態。まだ結果が出ていない。 |
fulfilled (成功) |
成功して resolve() が呼ばれた状態。 |
rejected (失敗) |
エラーが発生して reject() が呼ばれた状態。 |
Promiseクラスのコンストラクタ引数に関数を渡します。
その関数は、引数に成功(resolve)と失敗(reject)の2つ関数を取る関数を渡します。
(この辺で何を言っているかわからないですよね!)
その関数定義でif文の条件で、成功かエラーを分けています。
成功の場合は、resolve関数を呼ぶ。エラーの場合はreject関数を読んでいます。
resolve関数が呼ばれるとthen関数が呼ばれ、then関数のdata引数にresolveの引数の「データ取得成功」が渡され、console.logで「データ取得成功」が標準出力されます。
reject関数が呼ばれるとcatch関数が呼ばれ、catche関数のerr引数にrejectの引数の「データ取得失敗」が渡され、console.logで「データ取得失敗」が標準出力されます。
利点:then/catchによるチェーン構造で、ネストを減らせる。
複数の非同期処理を Promise.all()
などで組み合わせ可能。
欠点:then/catchチェーンも深くなると読みにくくなる。
async/awaitに比べると、同期的な見た目ではない。
- async / await とは
Promiseをより直感的で同期処理のように扱える構文。async
関数内で await
を使って、非同期結果を「待つ」。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (true) {
resolve("データ取得成功");
} else {
reject("データ取得失敗");
}
}, 1000);
});
}
async function main() {
try {
const result = await fetchData();
console.log(result); // "データ取得完了"
} catch (error) {
console.error(error);
}
}
main();
fetchData関数でPromiseを使っています。main関数で非同期処理を含む処理を呼び出しているので、main関数の定義部分を「async function main() {」とasyncをつけています。
main関数の本体で、非同期処理を読み出す(const result = await fetchData();))ときは、awaitをつけます。const result = await fetchData(); の非同期処理が成功パターンの場合は、「 console.log(result);」が処理されます。
このように同期処理のようプログラミングができます。
const result = await fetchData(); の非同期処理が失敗パターンの場合は、すぐさまcatch文以降が実行されます。(resultに代入もされませんし、console.log(result);も処理されません)
利点:同期処理のようなコードで読みやすく、保守性が高い。
try/catchでエラー処理も自然に書ける。
欠点:await
を使うには async
関数が必要。
複数の非同期処理を並列で行うには工夫が必要(Promise.all
併用など)。
Promiseとasync/awaitを使ったプログラミングがモダンで、コードの読みやすさや、保守性につながります。
以上、「Javascritの非同期処理」でした。