トピックから始める
今日のインタビューで、async/await 、 Promise、setTimeoutの実行順序についての質問を見ました。
async function async1() {
console.log('async1 start');
await async2();
console.log('asnyc1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
console.log('promise1');
reslove();
}).then(function () {
console.log('promise2');
})
console.log('script end');
私が出した答えは次のとおりです。
script start
async1 start
async2
asnyc1 end // x
promise1
script end
promise2
setTimeOut
正解:
script start
async1 start
async2
promise1
script end
asnyc1 end
promise2
setTimeOut
asnyc1 が終了する前に、promise1 が表示されるのはなぜですか? という疑問を持って、イベントループの仕組みについて勉強してみました。
js EventLoopイベントループ機構
JavaScript には 2 種類のイベントがあります。
イベントの実行順序は、最初にマクロタスクを実行し、次にマイクロタスクを実行することです。これが基本です。タスクには同期タスクと非同期タスクがあります。同期タスクはメインスレッドに入り、非同期タスクはイベントテーブルに入り、登録されます関数. 非同期イベントが完了すると、コールバック関数がイベント キューに配置されます (マクロ タスクとマイクロ タスクは別のイベント キューです). 同期タスクが実行された後、イベントはイベント キューから読み取られて配置されますコールバック関数には別のタスクが含まれる場合もあるため、上記の操作はループ内で実行されます。
注: setTimeOut は、コールバック関数を上記の非同期キューに直接配置しませんが、タイマー時間が経過した後にコールバック関数を実行非同期キューに配置します。この時点でこのキューにすでに多くのタスクがある場合は、それらのタスクをその後ろにキューに入れます。これは、setTimeOut が正確に実行できない理由も説明します。setTimeOut を実行するには、次の 2 つの条件を満たす必要があります。
メイン プロセスはアイドル状態である必要があります。時間が経過した場合、メイン プロセスがアイドル状態でない限り、コールバック関数は実行されません。
このコールバック関数は、コールバック関数がコールバック関数に挿入されたときに前の非同期関数がすべて実行されるまで実行されません。非同期キュー。
約束、非同期/待機
まず、新しい Promise は同期タスクであり、すぐに実行できるようにメイン プロセスに配置されます。.then() 関数は、非同期タスクが非同期キューに配置されることを意味します。タスクはいつ非同期キューに配置されますか? Promise 状態が終了すると、すぐに非同期キューに入れられます。
async キーワードを持つ関数は Promise オブジェクトを返します。await がなければ、実行は通常の関数と同等です。await がなければ、async 関数はあまり強力ではありません。
await キーワードは、async キーワード関数の内部にある必要があります。await が外部で記述された場合、エラーが報告されます。await は、そのセマンティクスと同様に、右側の式が完了するのを待機します。このとき、await はスレッドを離れ、非同期内で後続のコードをブロックし、非同期外のコードを最初に実行します。内部の後続のコードは、外部の同期コードが実行されるまで実行されません。await が Promise オブジェクトではなく同期関数である場合でも、この操作を待機します。
工程仕分け
上記のコードの実行プロセスを全体的に見てみましょう。
- コード スニペット (スクリプト) 全体は、console.log('script start') をマクロ タスクとして実行し、script start を出力します。
- setTimeout の実行は非同期アクションであり、マクロ タスクの非同期キューに入れられます。
- async1() を実行し、async1 start を出力し、実行を継続します。
- async2() を実行し、async2 を出力し、Promise オブジェクトを返します。await はスレッドを放棄し、返された Promise をマイクロタスクの非同期キューに追加します。そのため、async1() 以下のコードも、実行を続ける前に上記の処理が完了するまで待機する必要があります。
- 新しい Promise を実行し、promise1 を出力し、resolve() をマイクロタスクの非同期キューに入れます。
- console.log('script end') を実行し、script end を出力します。
- この時点で、すべての同期コードが実行され、マイクロタスクの非同期キューに移動してタスクを取得します。
- 次に、resolve (async2 によって返された Promise によって返される) が実行され、async1 end が出力されます。
- 次に、resolve (new Promise) を実行し、promise2 を出力します。
- 最後にsetTimeoutを実行し、settimeoutを出力します。
ステップ 4 には、await のメカニズムがあり、外部関数の実行をブロックしません。await が Promise を待機している場合でも、Promise 内のコードは同期的に実行されます。 Promise、Promise が使用されます。.resolve でカプセル化します。async2 は非同期メソッドです。内部の出力は同期的に実行され、await async2() の後のコードはマイクロタスク キューの最初の位置に配置され、実行される外部同期コードを実装します。
これで、スクリプトの終了出力が async1 終了よりも優先される理由がわかりました。