【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の使用例
メリット
- async/awaitは、非同期処理を同期的なコードのように記述できるため、コードの可読性が大幅に向上しました。
- try-catchブロックでエラーをキャッチできるため、エラー処理が簡素化できます。
使える環境
- Node.js ならv7.6以降でサポートされています。
- ブラウザは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秒程度となっています。コード的にもスッキリした感じで可読性が向上しています。
以上になります。