【JavaScript】async/awaitを使った非同期/同期処理について

以前Python3での非同期/同期処理について【Python3】async/awaitを使った非同期/同期処理の学習で簡単に触れました。比較のため、JavaScriptについても同様に学習してみました。JavaScriptのasync/awaitは、ECMAScript 2017(ES8)から正式に導入されました。async/await登場する前後でどのようにコードが変化したかメモしておきます。

async/awaitの登場以前

非同期処理は主にコールバック関数を使っていました。ここで、非同期処理のコールバック関数の処理結果の順番が重要である時などは、実行結果を待って次の処理、さらに次の処理と順番を決めてコードを記述しなければなりません。この場合async/awaitが登場する以前では、ネスト構造の多重ループで記述しなければなりませんでした。

コールバック地獄

サンプル1は、ループ(1)→ループ(2)→ループ(3)と同期的に処理を進めていくためのサンプルコードです。

サンプル1

function task(msec){
    const start = performance.now();
    
    setTimeout(()=>{
        console.log(`外側ループ(1)${msec} msec待ちました`);
        setTimeout(()=>{
            console.log(`内側ループ(2)${msec} msec待ちました`);
            setTimeout(()=>{
                console.log(`さらに内側ループ(3)${msec} msec待ちました`);
                const end = performance.now();
            console.log(`${Math.floor((end - start)*100)/100} msec経過!!`);
            },msec)
        },msec)
    },msec);
}

function main(){
    console.log("start");
    task(1000);
    console.log("end");
    const endmain = performance.now();
}

main();

実行結果1

start
end               # ← ここでmain()処理から抜けます。
外側ループ(1)1000 msec待ちました   # (1)
内側ループ(2)1000 msec待ちました  # (2)
さらに内側ループ(3)1000 msec待ちました  # (3)
3016.69 msec経過!!        # ← トータル時間 = (1)+(2)+(3)

バックグランドでループ(1)→ループ(2)→ループ(3)と意図した順番に処理が進んでいることが確認できました。但しコードの可読性が悪く修正も大変なことが容易に予想されます。これがいわゆる「コールバック地獄」と呼ばれる状態でした。

async/awaitの使用例

メリット
  1. async/awaitは、非同期処理を同期的なコードのように記述できるため、コードの可読性が大幅に向上しました。
  2. try-catchブロックでエラーをキャッチできるため、エラー処理が簡素化できます。
使える環境
  1. Node.js ならv7.6以降でサポートされています。
  2. ブラウザはES2017に対応したバージョンからサポートされています。

コードで比較

非同期処理

サンプル2

 function task(msec){
    const start = performance.now();
    console.log(`${msec} msec待ち開始`);
    // console.log(`開始時間:${Date.now()}`);
    setTimeout(()=>{
        const end = performance.now();
        console.log(`${Math.floor((end - start)*100)/100} msec経過!!`);

    },msec);
}

function main(){
    console.log("start");
    task(1000);
    task(2000);
    console.log("end");

}

main();

実行画面2

start
1000 msec待ち開始
2000 msec待ち開始
end             # ← ここでmain()処理から抜けます。
1005.79 msec経過!!  # タスク(1)
2005.39 msec経過!!  # タスク(2)

2タスクを非同期(バックグランド)で同時に行うため、トータル時間は一番遅い処理結果(約2秒程度)となっていることが確認できました。

これを意図した通りの順番通りに実行させるには。

同期処理

サンプル3

 const wait = async (ms) => {
    // 時間計測開始
    const start = performance.now();
    // promise 関数
    return new Promise((resolve) => {
        setTimeout(() => {
            // 計測終了
            const end = performance.now();
            console.log(`${Math.floor((end - start)*100)/100} msec経過!!`);
            resolve();
        }, ms)
    });
}

const main = async () => {
    // console.log(new Date());
    console.log('開始');
    const start = performance.now();
    await wait(1000); 
    await wait(2000); 
    const end = performance.now();
    console.log(`合計時間:${Math.floor((end - start)*100)/100} msec経過!!`);
    // console.log(new Date());
    console.log('終了');
};

main();

実行結果3

開始                                                      # main()処理開始
1007.39 msec経過!!                       # タスク(1)終了
2007.6 msec経過!!                          # タスク(2)終了
合計時間:3015.6 msec経過!!    # ← トータル時間 = (1)+(2)
終了                                                      # main()処理終了
 await wait(1000);   # タスク(1)
 await wait(2000);   # タスク(2)

タスク(1)→タスク(2)と順番に処理を行っていることが分かります。またトータル時間はその合計の約3秒程度となっています。コード的にもスッキリした感じで可読性が向上しています。

以上になります。

Follow me!