PromisがsetTimeout()よりも速いのはなぜですか?
クレイジーテクノロジーハウスのフロントエンドのパイオニア
//デイリーフロントエンドナイトトークNo.468
//本体:1200ワード
//推定読書時間:10分
実験
最初に実験を行いましょう。すぐに解決されるPromisを見てみましょう。それとも、より高速に実行される即時タイムアウト(0ミリ秒のタイムアウト)を見てみましょう。
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});
setTimeout(function timeout() {
console.log('Timed out!');
}, 0);
// logs 'Resolved!'
// logs 'Timed out!'
Promise.resolve(1)は、すぐに解決されるpromiseを返す静的関数です。setTimeout(callback、0)は、0ミリ秒の遅延でコールバックを実行します。
実行を開き、コンソールを確認します。ログが最初に「Resolved!」と出力され、次に「Timeoutcompleted!」と出力されたことがわかります。すぐに解決されるプロミスは、即時タイムアウトよりも速く処理されます。
Promise.resolve(true).then(...)がsetTimeout(...、0)の前に呼び出されるため、promiseの処理が速くなりますか?
次に、実験条件を変更します。最初にsetTimeout(...、0)を呼び出します。
setTimeout(function timeout() {
console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});
// logs 'Resolved!'
// logs 'Timed out!'
コンソールを実行して確認すると、結果は同じです。
setTimeout(...、0)はPromise.resolve(true).then(...)の前に呼び出されますが、「Resolved!」は「Timedout!」の前に出力されます。
実験によると、すぐに解決されるプロミスは、すぐにタイムアウトする前に処理されています。など。。。何故ですか?
イベントループ
非同期JavaScriptに関連する質問は、イベントループを調べることで回答できます。非同期JavaScriptがどのように機能するかを確認しましょう。
空のイベントループ
呼び出しスタック(呼び出しスタック)は、コードの実行中に作成された実行コンテキストを格納するために使用されるLIFO(後入れ先出し)構造です。つまり、コールスタックは関数を実行するために使用されます。
Web APIは非同期操作(フェッチ要求、プロミス、タイマー)であり、コールバックはここでの作業の完了を待ちます。
タスクキューは、実行の準備ができている非同期操作のコールバックを含むFIFO(先入れ先出し)構造です。たとえば、タイムアウトsetTimeout()コールバック(実行準備完了)がタスクキューに入ります。
ジョブキューは、実行されるプロミスのコールバックを含むFIFO(先入れ先出し)構造です。たとえば、解決された解決または拒否されたコールバックがワークキューに入ります。
最後に、イベントループは、コールスタックが空かどうかを常に監視します。コールスタックが空の場合、イベントループはワークキューまたはタスクキューを検索し、実行の準備ができているコールバックをコールスタックにデキューします。
作業キューとタスクキュー
イベントループの観点から前の実験を見てみましょう。コードの実行を徐々に分析していきます。
1.コールスタックはsetTimeout(...、0)を実行し、タイマーを「スケジュール」します。timeout()コールバックはWebAPIに保存されます。
setTimeout(function timeout() { console.log('Timed out!');}, 0);
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});
イベントループ
2。コールスタックはPromise.resolve(true).then(resolve)を実行し、promise解決を「調整」します。resolve()コールバックはWebAPIに保存されます。
setTimeout(function timeout() {
console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() { console.log('Resolved!');});
イベントループ
3。Promiseはすぐに解決され、タイマーはすぐに期限切れになります。このとき、タイマーコールバックtimeout()はタスクキューに「キュー」され、promiseコールバックresolve()はワークキューに「キュー」されます:
イベントループ
4。最も興味深い部分は次のとおりです。イベントループは、タスクの上に作業を置きます。イベントループは、promiseコールバックresolve()をワークキューからデキューしてコールスタックに配置し、次にコールスタックがpromiseコールバックresolve()を実行します。
setTimeout(function timeout() {
console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');});
「解決済み!」がコンソールに出力されます。
イベントループ
5。最後に、イベントループは、タイマーコールバックtimeout()をタスクキューからコールスタックに移動します。次に、コールスタックはタイマーコールバックtimeout()を実行します。
setTimeout(function timeout() {
console.log('Timed out!');}, 0);
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});
「タイムアウト!」がコンソールに出力されました。
現在、イベントループの
呼び出しスタックは空です。スクリプトの実行が完了しました。
総括する
すぐに解決されるプロミスが即時タイマーよりも速く処理されるのはなぜですか?
タスクキュー内のタスク(履行されたpromiseを格納するためのコールバック)がタスクキュー内のタスク(タイムアウトを格納するためのsetTimeout()コールバック)からデキューされるのは、イベントループの「優先度」によるものです。