同步任務與非同步任務執行順序

mmsmd發表於2021-10-06

JavaScript是單執行緒執行的,即 js 中任務是按順序依次執行的,但若其中一個任務執行時間過長,後續任務會一直等待,造成程式假死。 為了解決這個問題,將任務分為同步任務和非同步任務,其中非同步任務又分為巨集任務和微任務。

同步任務與非同步任務:

同步任務:又叫做非耗時任務,指的是在主執行緒上排隊執行的那些任務

       只有前一個任務執行完畢,才能執行後一個任務

非同步任務:又叫做耗時任務,非同步任務由JavaScript 委託給宿主環境進行執行 

     當非同步任務執行完成後,會通知JavaScript 主執行緒執行非同步任務的回撥函式

 

  1. 同步任務由JavaScript主執行緒次序執行
  2. 非同步任務委託給宿主環境執行
  3. 已完成的非同步任務對應的回撥函式,會被加入到任務佇列中等待執行
  4. JavaScript 主執行緒執行棧被清空後會讀取任務佇列中的回撥函式,次序執行
  5. JavaScript 主執行緒不斷重複上面的第4步

JavaScript主執行緒從“任務佇列”中讀取非同步任務的回撥函式,放到執行棧中依次執行。這個過程是迴圈不斷的,所以整個的這種執行機制又稱為 EventLoop (事件迴圈)

巨集任務與微任務

巨集任務:非同步Ajax請求,setTimeout,setInterval,檔案操作,new Promise等

微任務:Promise.then、.catch、.finally,process.nextTick等

 

巨集任務與微任務是交替執行的,每次執行完巨集任務都會檢查是否有微任務

程式碼示例:

console.log('A');

setTimeout(function() {
  console.log('B');
}, 0);

Promise.resolve().then(function() {
  console.log('C');
}).then(function() {
  console.log('D');
});

console.log('E');

先執行同步任務列印A和E,再執行非同步任務中的微任務,列印C和D,最後執行巨集任務列印B

最終列印結果:AECDB

可能有人會問,為什麼微任務優先於巨集任務執行,其實並不是,這裡先執行微任務的原因是,script本身也是一個巨集任務,這個巨集任務執行結果就是新增各種微任務與巨集任務,比如下面程式碼中,同步任務執行完成後,會先執行script的巨集任務,即新增一個setTimeout的巨集任務與一個Promise.then的微任務,這個巨集任務執行完成後,就該執行Promise.then的微任務了。並不是微任務優先順序大於巨集任務,而是這個巨集任務執行感知不強,會讓人感覺並沒有執行巨集任務,其實是同樣遵循上面流程,執行了巨集任務

下面是一個多層次程式碼,可進行練習:

 1 console.log('1')
 2 
 3 setTimeout(function () {
 4   console.log('2')
 5   process.nextTick(function () {
 6     console.log('3')
 7   })
 8   new Promise(function (resolve) {
 9     console.log('4')
10     resolve()
11   }).then(function () {
12     console.log('5')
13   })
14 })
15 Promise.resolve().then(function () {
16   console.log('6')
17 })
18 new Promise(function (resolve) {
19   console.log('7')
20   resolve()
21 }).then(function () {
22   console.log('8')
23 })
24 
25 setTimeout(function () {
26   console.log('9')
27   process.nextTick(function () {
28     console.log('10')
29   })
30   new Promise(function (resolve) {
31     console.log('11')
32     resolve()
33   }).then(function () {
34     console.log('12')
35   })
36 })

 

分析:

第一遍:

  1. 首先執行第一行的同步任務,列印1

  2. 第三行的setTimeout是非同步任務中巨集任務,加入巨集任務記為setTimeout1
  3. 下面第15行Promise.then是非同步任務中的微任務,加入微任務記為 then

  4. 第18行new Promise是同步任務,執行第一個log直接列印7,後面的 .then 是微任務,存入微任務中記為 then1

  5. 第25行setTimeout是非同步任務中巨集任務,加入巨集任務記為 setTimeout2

此時整個程式狀態如下:

 

 

第二遍:

  1. 首先執行微任務區中的任務,then列印6,then1列印8
  2. 微任務區任務執行完成,再執行巨集任務區 setTimeout1 ,列印2,將第5行process微任務放入微任務區記作 process2
  3. 第8行new Promise為同步任務,立即執行列印4,將後續 .then 微任務 放入微任務區記作 then2

此時整個程式狀態如下:

 

 

第三遍:

  1. 首先執行微任務區,process2,列印3,再執行then2,列印5 
  2. 微任務區執行完成,再去執行巨集任務區中的setTimeout2,首先是第26行log直接列印9
  3. 將第27行process微任務放入微任務區記作 process3
  4. 第30行new Promise為同步任務,立即執行列印11,將後續 .then 微任務 放入微任務區記作 then3

此時整個程式狀態如下:

 

最後一遍:

  1. 執行微任務區process3,列印10
  2. 執行微任務區then3,列印12

最終列印結果:1,7,6,8,2,4,3,5,9,11,10,12

 

相關文章