await
を使う理由は、別の処理に実行権を譲るためです。
JavaScriptはシングルスレッドであるため、実行権を持つ処理(以降「主処理」と呼びます)が自ら権利を手放さない限り、他の処理が実行されることはありません。主処理が10秒以上かかるような処理であっても同じです。ですので、その間は、主処理以外の処理によってコンテキスト(=DOMの状態やグローバル変数の値など)が書き換えられることはありません。
以下のコードを実行すると、スリープ前とスリープ後の出力は必ず同じになります。
const sleep = msec => {
const start = new Date().getTime();
while (new Date().getTime() - start < msec);
};
(() => {
console.log(window.scrollY); //=> 0
sleep(10000);
console.log(window.scrollY); //=> 0
})();
どれだけマウスやキーボードで操作しようとしても、出力内容は同じになります。そもそもJavaScriptはシングルスレッドであり、主処理が実行権を手放さない限り他の処理は実行されないため、スリープ中にマウスやキーボードでコンテキストを書き換えることはできません。
awaitを使う理由
重い処理をしている間もマウスやキーボードで操作できるようにしたい、複数の処理を並行して実行したいという場合、各処理が実行権を譲り合うよう実装されている必要があります。この「実行権を譲る実装」に使われるものがawait
です。
以下のコードを実行すると、スリープ中も画面をスクロールできます。
const sleep = async msec => {
return new Promise(resolve => {
window.setTimeout(() => resolve(), msec);
});
};
(async () => {
console.log(window.scrollY); //=> 0
await sleep(10000);
console.log(window.scrollY); //=> 1051
})();
スクロールした結果、スリープ前とスリープ後の出力内容が変わっています。このように、await
を使うことで主処理が実行権を手放すことができ、「画面をスクロールする」という処理に実行権を移すことができます。
awaitの難しいところ
await
により他の処理に実行権が移ると、その処理によってコンテキストが書き換えられる可能性があります。先程の例が該当するのですが、スクロールすることでwindow.scrollY
の値が書き換わっています。このように、await
の後でコンテキストが書き換わっていることがあるため、await
行以降の処理を実装する際は、コンテキストの書き換わりを考慮する必要があります。
console.log(window.scrollY);
await sleep(10000);
/*
* コンテキストの書き換わりを考慮した実装
*/
console.log(window.scrollY);
コンテキストの書き換わりを考慮する必要があるため、await
を使わない実装に比べてプログラムが複雑になります。
まとめ
await
を使う理由は「別の処理に実行権を譲るため」であり、await
の難しいところは「コンテキストの書き換わりを考慮する必要があること」です。