EventLoop-瀏覽器篇2

最近又碰到了event loop問題,之前研究的實在是淺顯(🔗//www.cnblogs.com/zx0423/p/12641637.html)所以今天主要講述promise的鏈式調用,async/await,requestAnimationFrame以及MutationObserver()。

1、Promise鏈式調用

 1    new Promise((resolve,reject)=>{
 3         console.log("promise1");
 4         resolve();
 5     }).then(()=>{
 6         //callback1
 7         console.log("then11");
 8         new Promise((resolve,reject)=>{
 9             console.log("promise2");
10             resolve();
11         }).then(()=>{
12             // callback2
13             console.log("then21");
14         }).then(()=>{
15             // callback4
16             console.log("then23");
17         })
18     }).then(()=>{
19         // callback3
20         console.log("then12");
21     })

/*
1、new promsie是同步程式碼,立即執行
2、外部promise第一個then需要等外部promise被resolve後才執行,外部promise立即被resolve,於是執行註冊的 then回調。此時外部第二then並未執行,因為他等待的是外部第一個then返回的promise
3、進入外部第一個then,回調裡面是一個promise,執行內部promise時,同樣兩個then鏈式調用,內部第一個then執 行後,內部promise算執行完,返回一個新的promise給內部第二then用
4、外部第一個then執行完且有返回值,進入外部第二then
5、之後執行內部第二then
*/

解釋:

// 行2 ,執行同步的executor函數
棧【 promise 】
宏任務隊列【】
微任務隊列【 callback1 】
執行結果:promise1

// 行7
棧【 callback1 】
宏任務隊列【】
微任務隊列【 callback2,callback3 】
執行結果:promise1 , then11 , promise2

//
棧【 callback2,callback3 】
宏任務隊列【】
微任務隊列【 callback4 】
執行結果:promise1 , then11 , promise2 , then21 , then12 ,

// 行16
棧【 callback4 】
宏任務隊列【】
微任務隊列【 】
執行結果:promise1 , then11 , promise2 , then21 , then12 , then23

 

⚠️ 當行8添加 return 語句的時候,答案就截然不同了,大家可以自行體會一下。

 

2、async/await

  • async函數

  • 返回一個 Promise 對象

    1 async function test() {
    2     return 1;   // async的函數 在這裡隱式 使用 Promise.resolve(1)
    3 }
    4 5 //===>等價於
    6 new Promise(function(resolve, reject) {
    7   resolve(1);
    8 })
  • await

    每次我們使用 await, 解釋器都創建一個 promise 對象,然後把剩下的 async 函數中的操作放到 then 回調函數中。async/await 的實現,離不開 Promise。

    1-await後面不是Promise對象,即是一些直接量 / 數值之類的
    • await會阻塞後面的程式碼,

      • 從右到左執行,

      • 再執行async外面的同步程式碼,

      • 同步程式碼執行完,再回到async內部,

      • 把這個非promise的東西,作為 await表達式的結果。

    2-await後面是Promise對象
    • await 它會「阻塞」後面的程式碼,

      • 從右到左執行,

      • 再執行async外面的同步程式碼,

      • 【期間不管宏任務微任務,就一直執行,直到…】

      • 直到 Promise 對象 resolved(也就是要等到了結果),即把 resolve 的 參數/值 作為 await 表達式的運算結果。

    理清上述執行順序,對下面這道題就可以迎刃而解了。

 1 // async/await 與 then鏈式調用
 2 async function f1(){
 3   console.log("1");
 4   await 'await的結果'
 5   console.log("3")
 6 }
 7 
 8 f1();
 9 
10 new Promise(function(resolve){
11   console.log("2")
12   resolve();
13 }).then(function(result){
14   new Promise( r => r()).then(() =>{
15       console.log(4)
16     }).then(()=>{
17     console.log(5);
18   }) 
19 })

結果: 1,2,3,4,5

 

3、requestAnimationFrame & MutationObserver()

基本概念: Window.requestAnimationFrame(callback):告訴瀏覽器——希望執行一個動畫,並且要求瀏覽器在下次重繪之前調用指定的回調函數更新動畫;
      MutationObserver():會在指定的DOM發生變化時被調用。【微任務,很好理解,按照正常的執行順序執行即可】

 大家先來看一段程式碼吧,思考一下他的執行結果是什麼?然後在刷新看看,結果還是不是一樣的了?

<div id="testElement"></div>
 1 setTimeout(() => {
 2   console.log(performance.now(), 'settimeout');
 3 }, 0)
 4 
 5 requestAnimationFrame(() => {                  
   console.log(performance.now(),'requestAnimationFrame'); 6 }) 7 8 const testElement = document.getElementById('testElement'); 9 10 var observer = new MutationObserver(() => { 11 console.log('MutationObserver'); 12 }); 13 14 observer.observe(testElement, { 15 childList: true 16 }) 17 //添加子節點,觸發MutationObserver() 18 const div = document.createElement('div'); 19 testElement.appendChild(div); 20 21 new Promise(resolve => { 22 console.log('promise'); 23 resolve(); 24 }).then(() => console.log('then')) 25 26 console.log(performance.now(), 'global');

如圖:

可以看到,requestAnimationFrame的執行順序是不一致的,

  起初,我將requestAnimationFrame歸到宏任務中,原因是它絕大多數都會在setTimeout回調執行之後才執行。並將這個結果解釋為是由於瀏覽器在執行渲染的時候,每次執行的時間會有差異,所以導致requestAnimationFrame和setTimeout被壓入調用棧的時機不一致,也就導致了回調的時間不一致。

但是,可以看到宏任務與raf任務有明顯的差異:
  執行時機不同;
  raf任務隊列被執行時,會將其此刻隊列中所有任務都執行完,既不屬於宏任務/微任務,同時還排在微任務之後。

而且,在一個loop中,可能並不會執行這一步,只要瀏覽器任務不需要執行渲染,就會跳過。所以會出現requestAnimationFrame順序不一致的狀況。