JS事件循環(huán)之宏任務(wù)和微任務(wù)

JS事件循環(huán)之宏任務(wù)和微任務(wù)

眾所周知臀栈,JS 是一門單線程語言月腋,可是瀏覽器又能很好的處理異步請求妄迁,那么到底是為什么呢疗韵?

JS 的執(zhí)行環(huán)境一般是瀏覽器和 Node.js兑障,兩者稍有不同,這里只討論瀏覽器環(huán)境下的情況蕉汪。

JS 執(zhí)行過程中會產(chǎn)生兩種任務(wù)流译,分別是:同步任務(wù)和異步任務(wù)。

  • 同步任務(wù):比如聲明語句者疤、for福澡、賦值等,讀取后依據(jù)從上到下從左到右驹马,立即執(zhí)行革砸。
  • 異步任務(wù):比如 ajax 網(wǎng)絡(luò)請求,setTimeout 定時函數(shù)等都屬于異步任務(wù)糯累。異步任務(wù)會通過任務(wù)隊列(Event Queue)的機(jī)制(先進(jìn)先出的機(jī)制)來進(jìn)行協(xié)調(diào)算利。

任務(wù)隊列(Event Queue)

任務(wù)隊列中的任務(wù)也分為兩種,分別是:宏任務(wù)(Macro-take)和微任務(wù)(Micro-take)

  • 宏任務(wù)主要包括:scrip(JS 整體代碼)泳姐、setTimeout效拭、setInterval、setImmediate、I/O缎患、UI 交互
  • 微任務(wù)主要包括:Promise(重點關(guān)注)慕的、process.nextTick(Node.js)、MutaionObserver

任務(wù)隊列的執(zhí)行過程是:先執(zhí)行一個宏任務(wù)挤渔,執(zhí)行過程中如果產(chǎn)出新的宏/微任務(wù)肮街,就將他們推入相應(yīng)的任務(wù)隊列,之后在執(zhí)行一隊微任務(wù)蚂蕴,之后再執(zhí)行宏任務(wù)低散,如此循環(huán)俯邓。以上不斷重復(fù)的過程就叫做 Event Loop(事件循環(huán))骡楼。

每一次的循環(huán)操作被稱為tick

image

理解微任務(wù)和宏任務(wù)的執(zhí)行執(zhí)行過程

console.log("script start");

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

Promise.resolve()
  .then(function () {
    console.log("promise1");
  })
  .then(function () {
    console.log("promise2");
  });

console.log("script end");

按照上面的內(nèi)容稽鞭,分析執(zhí)行步驟:

  1. 宏任務(wù):執(zhí)行整體代碼(相當(dāng)于<script>中的代碼):
    1. 輸出: script start
    2. 遇到 setTimeout鸟整,加入宏任務(wù)隊列,當(dāng)前宏任務(wù)隊列(setTimeout)
    3. 遇到 promise朦蕴,加入微任務(wù)篮条,當(dāng)前微任務(wù)隊列(promise1)
    4. 輸出:script end
  2. 微任務(wù):執(zhí)行微任務(wù)隊列(promise1)
    1. 輸出:promise1,then 之后產(chǎn)生一個微任務(wù)吩抓,加入微任務(wù)隊列涉茧,當(dāng)前微任務(wù)隊列(promise2)
    2. 執(zhí)行 then,輸出promise2
  3. 執(zhí)行渲染操作疹娶,更新界面(敲黑板劃重點)伴栓。
  4. 宏任務(wù):執(zhí)行 setTimeout
    1. 輸出:setTimeout

Promise 的執(zhí)行

new Promise(..)中的代碼,也是同步代碼雨饺,會立即執(zhí)行钳垮。只有then之后的代碼,才是異步執(zhí)行的代碼额港,是一個微任務(wù)饺窿。

console.log("script start");

setTimeout(function () {
  console.log("timeout1");
}, 10);

new Promise((resolve) => {
  console.log("promise1");
  resolve();
  setTimeout(() => console.log("timeout2"), 10);
}).then(function () {
  console.log("then1");
});

console.log("script end");

步驟解析:

  • 當(dāng)前任務(wù)隊列:微任務(wù): [], 宏任務(wù):[<script>]
  1. 宏任務(wù):
    1. 輸出: script start
    2. 遇到 timeout1,加入宏任務(wù)
    3. 遇到 Promise移斩,輸出promise1肚医,直接 resolve,將 then 加入微任務(wù)向瓷,遇到 timeout2忍宋,加入宏任務(wù)。
    4. 輸出script end
    5. 宏任務(wù)第一個執(zhí)行結(jié)束
  • 當(dāng)前任務(wù)隊列:微任務(wù)[then1]风罩,宏任務(wù)[timeou1, timeout2]
  1. 微任務(wù):
    1. 執(zhí)行 then1糠排,輸出then1
    2. 微任務(wù)隊列清空
  • 當(dāng)前任務(wù)隊列:微任務(wù)[],宏任務(wù)[timeou1, timeout2]
  1. 宏任務(wù):
    1. 輸出timeout1
    2. 輸出timeout2
  • 當(dāng)前任務(wù)隊列:微任務(wù)[]超升,宏任務(wù)[timeou2]
  1. 微任務(wù):
    1. 為空跳過
  • 當(dāng)前任務(wù)隊列:微任務(wù)[]入宦,宏任務(wù)[timeou2]
  1. 宏任務(wù):
    1. 輸出timeout2

async/await 的執(zhí)行

async 和 await 其實就是 Generator 和 Promise 的語法糖哺徊。

async 函數(shù)和普通 函數(shù)沒有什么不同,他只是表示這個函數(shù)里有異步操作的方法乾闰,并返回一個 Promise 對象

翻譯過來其實就是:

// async/await 寫法
async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
// Promise 寫法
async function async1() {
  console.log("async1 start");
  Promise.resolve(async2()).then(() => console.log("async1 end"));
}

看例子:

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log("timeout");
}, 0);
new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("promise2");
});
console.log("script end");

步驟解析:

  • 當(dāng)前任務(wù)隊列:宏任務(wù):[<script>]落追,微任務(wù): []
  1. 宏任務(wù):
    1. 輸出:async1 start
    2. 遇到 async2,輸出:async2涯肩,并將 then(async1 end)加入微任務(wù)
    3. 遇到 setTimeout轿钠,加入宏任務(wù)。
    4. 遇到 Promise病苗,輸出:promise1疗垛,直接 resolve,將 then(promise2)加入微任務(wù)
    5. 輸出:script end
  • 當(dāng)前任務(wù)隊列:微任務(wù)[promise2, async1 end]硫朦,宏任務(wù)[timeout]
  1. 微任務(wù):
    1. 輸出:promise2
    2. promise2 出隊
    3. 輸出:async1 end
    4. async1 end 出隊
    5. 微任務(wù)隊列清空
  • 當(dāng)前任務(wù)隊列:微任務(wù)[]贷腕,宏任務(wù)[timeout]
  1. 宏任務(wù):
    1. 輸出:timeout
    2. timeout 出隊,宏任務(wù)清空

注意:任務(wù)隊列(宏任務(wù)和微任務(wù))是棧(Stack)結(jié)構(gòu)咬展,執(zhí)行時遵循先進(jìn)后出(LIFO) 的原則

setTimerout 并不準(zhǔn)確

由上我們已經(jīng)知道了 setTimeout 是一個宏任務(wù)泽裳,會被添加到宏任務(wù)隊列當(dāng)中去,按順序執(zhí)行破婆,如果前面有涮总。

setTimeout() 的第二個參數(shù)是為了告訴 JavaScript 再過多長時間把當(dāng)前任務(wù)添加到隊列中。

如果隊列是空的祷舀,那么添加的代碼會立即執(zhí)行瀑梗;如果隊列不是空的,那么它就要等前面的代碼執(zhí)行完了以后再執(zhí)行蔑鹦。

看代碼:

const s = new Date().getSeconds();
console.log("script start");
new Promise((resolve) => {
  console.log("promise");
  resolve();
}).then(() => {
  console.log("then1");
  while (true) {
    if (new Date().getSeconds() - s >= 4) {
      console.log("while");
      break;
    }
  }
});
setTimeout(() => {
  console.log("timeout");
}, 2000);
console.log("script end");

因為then是一個微任務(wù)夺克,會先于setTimeout執(zhí)行,所以嚎朽,雖然setTimeout是在兩秒后加入的宏任務(wù)铺纽,但是因為then中的在while操作被延遲了4s,所以一直推遲到了4s秒后才執(zhí)行的setTimeout哟忍。

所以輸出的順序是:script start狡门、promise、script end锅很、then1其馏。
四秒后輸出:while、timeout

注意:關(guān)于 setTimeout 要補(bǔ)充的是爆安,即便主線程為空叛复,0 毫秒實際上也是達(dá)不到的。根據(jù) HTML 的標(biāo)準(zhǔn),最低是 4 毫秒褐奥。有興趣的同學(xué)可以自行了解咖耘。


總結(jié)

有個小 tip:從規(guī)范來看,microtask 優(yōu)先于 task 執(zhí)行撬码,所以如果有需要優(yōu)先執(zhí)行的邏輯儿倒,放入 microtask 隊列會比 task 更早的被執(zhí)行。

最后的最后呜笑,記住夫否,JavaScript 是一門單線程語言,異步操作都是放到事件循環(huán)隊列里面叫胁,等待主執(zhí)行棧來執(zhí)行的凰慈,并沒有專門的異步執(zhí)行線程。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末急鳄,一起剝皮案震驚了整個濱河市谤民,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疾宏,老刑警劉巖张足,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坎藐,居然都是意外死亡为牍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門岩馍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碉咆,“玉大人,你說我怎么就攤上這事蛀恩∫咄” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵双谆,是天一觀的道長壳咕。 經(jīng)常有香客問我,道長顽馋,這世上最難降的妖魔是什么谓厘? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮寸谜,結(jié)果婚禮上竟稳,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好他爸,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布地啰。 她就那樣靜靜地躺著,像睡著了一般讲逛。 火紅的嫁衣襯著肌膚如雪亏吝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天盏混,我揣著相機(jī)與錄音蔚鸥,去河邊找鬼。 笑死许赃,一個胖子當(dāng)著我的面吹牛止喷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播混聊,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼弹谁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了句喜?” 一聲冷哼從身側(cè)響起预愤,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咳胃,沒想到半個月后植康,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡展懈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年销睁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片存崖。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡冻记,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出来惧,到底是詐尸還是另有隱情冗栗,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布违寞,位于F島的核電站贞瞒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏趁曼。R本人自食惡果不足惜军浆,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挡闰。 院中可真熱鬧乒融,春花似錦掰盘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至申钩,卻和暖如春次绘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撒遣。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工邮偎, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人义黎。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓禾进,卻偏偏與公主長得像,于是被迫代替她去往敵國和親廉涕。 傳聞我的和親對象是個殘疾皇子泻云,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345