JavaScriptの非同期処理




こんにちは!

プロクラスの国府です。

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の非同期処理」でした。