https://jsprimer.net/basic/async/

この章ではJavaScriptの非同期処理について学んでいきます。 非同期処理はJavaScriptにおけるとても重要な概念です。 また、ブラウザやNode.jsなどのAPIには非同期処理のみのものも多いため、非同期処理を避けることはできません。 そのため、JavaScriptには非同期処理を扱うためのPromiseというビルトインオブジェクト、さらにはAsync Functionと呼ばれる構文的なサポートがあります。

この章では非同期処理とはどのようなものかという話から、非同期処理での例外処理、非同期処理の扱い方を見ていきます。

同期処理

多くのプログラミング言語にはコードの評価の仕方として、同期処理(sync)と非同期処理(async)という大きな分類があります。

今まで書いていたコードは同期処理と呼ばれているものです。 同期処理ではコードを順番に処理していき、ひとつの処理が終わるまで次の処理は行いません。 同期処理では実行している処理はひとつだけとなるため、とても直感的な動作となります。

一方、同期的にブロックする処理が行われていた場合には問題があります。 同期処理ではひとつの処理が終わるまで、次の処理へ進むことができないためです。

次のコードのblockTime関数は指定したtimeoutミリ秒だけ無限ループを実行し、同期的にブロックする処理です。 このblockTime関数を呼び出すと、指定時間が経過するまで次の処理(次の行)は呼ばれません。

// 指定した`timeout`ミリ秒経過するまで同期的にブロックする関数
function blockTime(timeout) {
    const startTime = Date.now();
    // `timeout`ミリ秒経過するまで無限ループをする
    while (true) {
        const diffTime = Date.now() - startTime;
        if (diffTime >= timeout) {
            return; // 指定時間経過したら関数の実行を終了
        }
    }
}
console.log("処理を開始");
blockTime(1000); // 他の処理を1000ミリ秒(1秒間)ブロックする
console.log("この行が呼ばれるまで処理が1秒間ブロックされる");

同期的にブロックする処理があると、ブラウザでは大きな問題となります。 なぜなら、JavaScriptは基本的にブラウザのメインスレッド(UIスレッドとも呼ばれる)で実行されるためです。 メインスレッドは表示の更新といったUIに関する処理も行っています。 そのため、メインスレッドが他の処理で専有されると、表示が更新されなくなりフリーズしたようになります。

先ほどの例では1秒間も処理をブロックしているため、1秒間スクロールなどの操作が効かないといった悪影響がでます。

非同期処理

非同期処理はコードを順番に処理していきますが、ひとつの非同期処理が終わるのを待たずに次の処理を評価します。 つまり、非同期処理では同時に実行している処理が複数あります。

JavaScriptにおいて非同期処理の代表的な関数としてsetTimeout関数があります。 setTimeout関数はdelayミリ秒後に、コールバック関数を呼び出すようにタイマーへ登録する非同期処理です。

setTimeout(コールバック関数, delay);

次のコードではsetTimeout関数を使って10ミリ秒後に、1秒間ブロックする処理を実行しています。 setTimeout関数でタイマーに登録したコールバック関数は非同期的なタイミングで呼ばれます。 そのためsetTimeout関数の次の行に書かれている同期的処理は、非同期処理よりも先に実行されます。

// 指定した`timeout`ミリ秒経過するまで同期的にブロックする関数
function blockTime(timeout) {
    const startTime = Date.now();
    while (true) {
        const diffTime = Date.now() - startTime;
        if (diffTime >= timeout) {
            return; // 指定時間経過したら関数の実行を終了
        }
    }
}

console.log("1. setTimeoutのコールバック関数を10ミリ秒後に実行します");
setTimeout(() => {
    console.log("3. ブロックする処理を開始します");
    blockTime(1000); // 他の処理を1秒間ブロックする
    console.log("4. ブロックする処理が完了しました");
}, 10);
// ブロックする処理は非同期なタイミングで呼び出されるので、次の行が先に実行される
console.log("2. 同期的な処理を実行します");

このコードを実行した結果のコンソールログは次のようになります。

  1. setTimeoutのコールバック関数を10ミリ秒後に実行します