序文
//
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout')
}, 0);
async1();
new Promise (function(resolve) {
console.log('promise1');
resolve()
}).then(function () {
console.log('promise2');
});
console.log('script end');
このコードはJSの非同期を総合的に検討したもので、イベントループ、マクロタスク/マイクロタスクなどの知識がないと、この問題の正解が書けないかもしれません。少なくとも私は既に失神しているのを見たことがあります。大丈夫です、浅いところから深いところまで、コード実行の過程で丁寧に口うるさく言いながら、実は優先順位の分け方を教えてくれています。
JSの実行方法
まず知っておかなければならないのは、JSはシングルスレッドであり、一般論として、一度に一つのことしかできません。そのため、setTimeout待ちイベントなどのイベントが発生した場合、実際にそのタイマーがカウントを終了し、実行をダウンさせるために待つことができない、これはあまりにも非効率的です。これはあまりにも非同期をもたらします。例えば
console.log('1');
setTimeout(()=>{
console.log('2')
}, 0);
console.log('3');
// 1
// 3
// 2
この遅延を0にしても、まず3が実行され、次に2が実行されることがわかります。では、上記のsetTimeoutの他に、非同期処理のトリガーとなるコードには何があるでしょうか?setInterval、Ajax、promise、async/awaitがあります!
では、非同期はどのように実装されるのでしょうか?非同期はコールバックに基づいて実装され、イベントループは非同期コールバックを実装するための基本原理です。
イベントループ
前述したように、JSはシングルスレッドなので、メインスレッドと呼ばれるコードがステップごとに実行されます。メインスレッドが非同期処理に遭遇すると、その非同期タスクをタスクキューに格納し、メインスレッドの実行を続けます。メインスレッドが実行を終了すると、イベントループを通してタスクキューを要求し、非同期コードを実行します。
イベントループはイベントポーリング機構とも呼ばれ、実際にはメインスレッドがタスクキューにコールバックを要求し続けるための機構です。キューに実行可能な非同期タスクが照会られると、タスクはコールスタックにプッシュされ、実行されます。
Dom操作は非同期ではありませんが、イベント・ループに基づくコールバックでもあることは特筆に値します。
マクロ・タスクとマイクロ・タスク
マクロタスクとマイクロタスクについて説明しながら、例を見てみましょう。
console.log('1');
setTimeout(()=>{
console.log('2')
}, 0);
Promise.resolve().then(()=> {
console.log('3')
})
console.log('4');
// プリントの順番は1、4、3だ。2
面白いですね!非同期は順番に実行されるはずですが、上の例では3が2の前に出力されているのがわかります。ここで、マイクロタスクとマクロタスクの概念と、それらの実行順序について説明します。
ここでまずいくつかの結論が得られます
- マクロタスクには、Ajax、Dom操作、setTimeout/setIntervalがあります。
- マイクロタスクには、promise、async/awaitが含まれます。
- マイクロタスクはマクロタスクよりも早く実行されます。
最初の2点は覚えておいて損はありません。3点目の理由については、詳しく話したり、コードで示したりします。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="container"></div>
<script src="https://..////../.js"></>
<script src="1.js"></script>
</body>
</html>
const $tag = $('<p>1</p>');
$('#container').append($tag);
console.log($('#container').children().length) // domノードを追加するコードを同期させた
// alertのブロックメカニズムを使って、レンダリングするかどうかを判断する。
Promise.resolve().then(()=> {
console.log('マイクロタスクを行う場合length', $('#container').children().length) // 1
alert('promise')
});
setTimeout(() => {
console.log('が入力されたらマクロタスクを実行する。length', $('#container').children().length) // 1
alert('settimeout')
}, 0);
プロミスがトリガーされたとき、同期コードでコンテナに新しいノードが追加されているにもかかわらず、domはまだレンダリングされていないことが簡単にわかります。
一般的な考え方を書きます。まず、非同期関数の定義は、同期コードの最初の実行に従って、実行されず、その後、非同期コードの原則の実装は、最初のプリントアウトは、スクリプトの開始であり、その後、マクロタスクであるsettimeoutに、最初に入れ、その後、async1()関数の実装は、async1の開始を印刷し、その後、async2をトリガし(awaitの次の文は、プロミス内のthenと等価であるため、async2を印刷するので、実際には非同期コールバックに属し、ここでasync1終了が実行されませんが、最初に一時的にタスクキューに格納され、プロミスの初期化は、最初の関数のパラメータの直接実装である同期コードと等価である、つまり、プリントのPromise1は、その後の内容も非同期であり、タスクキューに格納され、この時間の同期コードの実行が完了すると、非同期部分の実装の開始です。最初の非同期タスクは、async1終了を印刷し、その後、プロミスthenでpromise2を印刷し、最後にマクロタスク、setTimeoutを実行することです。
まとめ
JSの実行手順は以下の通りです。
1, 同期コードの最初の行実行
2、非同期が発生し、最初に一時的にタスクキューに格納されます。
3は、同期実行が完了すると、タスクキュータスクの実行を開始します。
4、マイクロタスクの実行
5、ドムレンダリング
6、マクロタスクの実行
このステップさえ覚えておけば、非同期に関するどんなコードも簡単に書けると思います。
Ps.今回初めて技術系の記事を書きましたが、非常に面白く、元々他の人の記事を読んで理解した気になっていたのですが、実際に書いてみると、記事の構成を考えたり、分かりやすく伝えるにはどうしたらいいかなど、ブレインストーミングのような作業でもあることが分かりました。インプットからアウトプットまでが本当に大変だと実感しました。 もし間違いがあれば、訂正してください、議論することを歓迎します!
なかなか書けないので、転載が必要な方はご連絡ください!





