ReactNative 擴(kuò)展篇之《javaScript promise await 宏任務(wù) 微任務(wù)》

由于接觸JavaScript斷斷續(xù)續(xù)也有挺長段時(shí)間,從以前做Web開發(fā)開始,到后來做移動(dòng)端解除到ReactNative,NodeJS,對這門語言一直在邊做邊學(xué),邊學(xué)邊深入,所以還是想記錄一些自己的所學(xué)所得吧,也當(dāng)做是自己的總結(jié),因?yàn)镴avaScript這門語言老實(shí)話確實(shí)不太好掌握,細(xì)節(jié)還是挺多的,而且一直在更新,咋們今天還是花點(diǎn)時(shí)間聊聊JavaScript里面的Promise,await ,微任務(wù),宏任務(wù)的內(nèi)容吧,為了我下面要寫flutter的 Future ,事件隊(duì)列,微任務(wù)的內(nèi)容來做一個(gè)鋪墊和對比,好的廢話不多說了,我們開始吧:

讓我們先從概念開始了解下這兩個(gè)任務(wù)是什么:
首先JavaScript的任務(wù)分為宏任務(wù)(macrotask),微任務(wù)(microtask)兩種

macrotask(又稱之為宏任務(wù)),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)
每一個(gè)task會(huì)從頭到尾將這個(gè)任務(wù)執(zhí)行完畢,不會(huì)執(zhí)行其它,瀏覽器為了能夠使得JS內(nèi)部task與DOM任務(wù)能夠有序的執(zhí)行,會(huì)在一個(gè)task執(zhí)行結(jié)束后,在下一個(gè) task 執(zhí)行開始前,對頁面進(jìn)行重新渲染

microtask(又稱為微任務(wù)),可以理解是在當(dāng)前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
也就是說,在當(dāng)前task任務(wù)后,下一個(gè)task之前,在渲染之前,所以它的響應(yīng)速度相比setTimeout(setTimeout是task)會(huì)更快,因?yàn)闊o需等渲染,也就是說,在某一個(gè)macrotask執(zhí)行完后,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)

分別很么樣的場景會(huì)形成macrotask和microtask呢?

macrotask:主代碼塊,setTimeout,setInterval等(可以看到,事件隊(duì)列中的每一個(gè)事件都是一個(gè)macrotask)

microtask:Promise,process.nextTick等

OK了解了這兩個(gè)概念以后,我們再來看一下事件隊(duì)列的執(zhí)行機(jī)制,如下圖(借鑒拿來的圖):

image.png

稍微解釋下上面圖的運(yùn)行流程(宏任務(wù),微任務(wù)的定義看上面):

  1. 執(zhí)行一個(gè)宏任務(wù)(棧中沒有就從事件隊(duì)列中獲?。?/li>
  2. 執(zhí)行過程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊(duì)列中
  3. 宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
  4. 當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染
  5. 渲染完畢后,JS線程繼續(xù)接管,開始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲?。?/li>

了解了上面幾個(gè)概念以后(如果沒有理解的請仔細(xì)閱讀),我們來幾個(gè)例子來檢驗(yàn)下“真理”,首先我們先看看promise的例子:

setTimeout(function() {
    console.log('setTimeout');
})

new Promise(function(resolve) {
    console.log('promise');
    resolve(true)
}).then(function() {
    console.log('then');
})

console.log('console');

// promise
// console
// then
// setTimeou

首先會(huì)遇到setTimeout,將其放到下一個(gè)宏任務(wù)(相對于主任務(wù)的宏任務(wù))event queue里面,然后回到 promise , new promise 會(huì)立即執(zhí)行, then會(huì)分發(fā)到微任務(wù),遇到 console 打印立即執(zhí)行,整體宏任務(wù)執(zhí)行完成,接下來判斷是否有微任務(wù),剛剛放到微任務(wù)里面的then,執(zhí)行then打印,ok第一輪宏任務(wù)事件結(jié)束,進(jìn)行第二輪宏任務(wù),剛剛我們放在event queue 的setTimeout 函數(shù)進(jìn)入到宏任務(wù),立即執(zhí)行

注意這里的 new promise 里面的同步代碼可以理解為宏任務(wù)的代碼所以可以立即執(zhí)行(這里是要注意的)

為什么上面我們說的Promise調(diào)用then方法會(huì)分發(fā)到微任務(wù)呢,我們來看看下面這個(gè)例子:

function sleep(second) {
    return new Promise((resolve, reject) => {
            resolve(5);
    })
}
console.log("主線程1")
var p = sleep(0);
Promise.resolve(12345).then(val => console.log(val));
p.then(val1 => console.log(val1));
console.log("主線程2")

//主線程1
//主線程2
//12345
//5

稍微解讀一下,首先打印主線程宏任務(wù)的句子“主線程1”,然后進(jìn)入到sleep方法里面返回的是一個(gè)fullfilled狀態(tài)的Promise賦值給變量p,然后Promise打印12345進(jìn)入微隊(duì)列,p再調(diào)用then方法進(jìn)入微隊(duì)列打印5,按照微隊(duì)列的執(zhí)行順序來說先加入微隊(duì)列的先執(zhí)行,所以p調(diào)用then是后加入微隊(duì)列的所以最后打印,這也說明了Promise是調(diào)用then方法才加入到微隊(duì)列的,而不是在sleep函數(shù)里面調(diào)用resolve方法的時(shí)候加入微隊(duì)列的

上面還算是比較好理解的,好的,再來一個(gè)復(fù)雜的:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

分析下順序:

  1. 首先先執(zhí)行console.log(1) 然后將setTimeout放到宏任務(wù)event queue里面 記作 setTimeout 1 ,接著 看到 process.nextTick ,將其放到微任務(wù)里面 ,記作 process 1,

  2. 然后 看到new promise 立即執(zhí)行輸出7,將里面的then 放到 微任務(wù)里面 記作 then 2, 繼續(xù),遇到 setTimeout 放到宏任務(wù)里面記作 setTimeout 2 。目前輸出的是:1,7,

  3. OK, 接下來,開始判斷是否有微任務(wù),剛剛放入到微任務(wù)event queue的進(jìn)入到主程序開始執(zhí)行,process 1 , then 2 目前輸出的是:6,8、

  4. 接下來,微任務(wù)的event queue 空了,進(jìn)行下一輪事件,將剛剛放到宏任務(wù)的 setTimeout 1 進(jìn)入到主線程遇到 console 立即執(zhí)行, 遇到 process.nextTick 放到微任務(wù) event queue 里面 記作 process1, 接著遇到 new Promise 立即執(zhí)行, 將 then 放到event queue 里面 記作 then 2,OK,當(dāng)前宏任務(wù)里的任務(wù)執(zhí)行完了,判斷是否有微任務(wù),發(fā)現(xiàn)有 process1, then 2 兩個(gè)微任務(wù) , 一次執(zhí)行 目前輸出的是:2,4,3,5、

  5. 目前主線程里的任務(wù)都執(zhí)行結(jié)束了,又開始第三輪事件循環(huán),同上(字太多,省略。。。。) 目前輸出的是:9,11,10,12、

好的,與Promise,setTimeout相關(guān)的宏任務(wù),微任務(wù)大家應(yīng)該掌握得差不多了,下面繼續(xù)解說結(jié)合await的時(shí)候會(huì)是什么樣子的呢?

我們先來復(fù)習(xí)下基礎(chǔ):

若 async 定義的函數(shù)有返回值,return 123;相當(dāng)于Promise.resolve(123)

async function demo01() {
    return 123;
}

demo01().then(val => {
    console.log(val);// 123
});

沒有聲明式的 return則相當(dāng)于執(zhí)行了Promise.resolve();

async function demo01() {
    console.log("withOut return")
}

demo01().then(val => {
    console.log(val);// undefined
});

await 可以理解為是 async wait 的簡寫。await 必須出現(xiàn)在 async 函數(shù)內(nèi)部,不能單獨(dú)使用,下面是有問題的調(diào)用:

function notAsyncFunc() {
    await Math.random();
}
notAsyncFunc();//Uncaught SyntaxError: Unexpected identifier

await 后面可以跟任何的JS 表達(dá)式。雖然說 await 可以等很多類型的東西,但是它最主要的意圖是用來等待 Promise 對象的狀態(tài)被 resolved。

如果 await 的是 promise對象,await 會(huì)暫停 async 函數(shù)內(nèi)后面的代碼,先執(zhí)行 async 函數(shù)外的同步代碼(注意,promise 內(nèi)的同步代碼會(huì)先執(zhí)行),等著 Promise 對象 fulfilled(這點(diǎn)非常重要),然后把 resolve 的參數(shù)作為 await 表達(dá)式的運(yùn)算結(jié)果返回后,再繼續(xù)執(zhí)行 async 函數(shù)內(nèi)后面的代碼,我個(gè)人理解這種情況下await函數(shù)就類似與Promise.then()函數(shù)作用,只有Promise狀態(tài)變成了fulfilled的話then函數(shù)才會(huì)執(zhí)行

下面是我的個(gè)人理解:

我的個(gè)人理解await后面的代碼或者表達(dá)式只要遇到promise ,以后的代碼可以看成是一個(gè)微隊(duì)列來執(zhí)行,類似await前面的代碼可以看做是Promise的同步宏任務(wù)執(zhí)行,遇到resolve 代碼(async 函數(shù)中不指定返回值,則會(huì)默認(rèn)返回resolve(undefined))就變成了一個(gè)微隊(duì)列,此時(shí)微隊(duì)列應(yīng)該是在主宏隊(duì)列后面執(zhí)行的,所以await后面的代碼會(huì)被“阻塞”(await以后的代碼可以看成是一個(gè)微隊(duì)列來執(zhí)行,因?yàn)槭俏㈥?duì)列不會(huì)立馬執(zhí)行,轉(zhuǎn)而執(zhí)行主宏隊(duì)列的任務(wù)),而await后面的代碼類似于Promise后面的then()函數(shù)執(zhí)行的代碼,只有在Promise變成fulfilled狀態(tài)才會(huì)繼續(xù)執(zhí)行

先來看一個(gè)例子:

function sleep(second) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('sleep');
        }, second);
    })
}
async function awaitDemo() {
    let result = await sleep(0);
    console.log(result?result:0);
}
console.log("主線程1")
Promise.resolve(123).then(res => console.log(res))
awaitDemo();
console.log("主線程2")
//主線程1
//主線程2
//123
//sleep

執(zhí)行順序:

  1. 先打印”主線程1” ,然后遇到Promise.then()放到微隊(duì)列任務(wù)里面,然后到await sleep(0); 進(jìn)入到sleep函數(shù)里面,

  2. 然后里面遇到promise的setTimeout放到宏隊(duì)列里面,await 會(huì)暫停 async 函數(shù)內(nèi)后面的代碼(等待Promise調(diào)用resolve方法再執(zhí)行),先執(zhí)行 async 函數(shù)外的同步代碼打印”主線程2” ,

  3. 宏隊(duì)列里面的主任務(wù)執(zhí)行完了,然后執(zhí)行微隊(duì)列里面打印123,然后執(zhí)行下一個(gè)宏隊(duì)列里面的任務(wù)resolve('sleep');

  4. 然后promise狀態(tài)變?yōu)閒ullfilled,await后面的代碼開始執(zhí)行打印sleep

如果你把setTimeout函數(shù)里面的resolve('sleep’);去掉的話,那么await函數(shù)會(huì)一直等待,后面的代碼永遠(yuǎn)不會(huì)執(zhí)行,大家可以試一下

我們在這個(gè)例子改變些代碼再來觀察下:


function sleep(second) {
    return Promise.resolve(111);
}
async function awaitDemo() {
    let result = await sleep(0);
    console.log(result?result:0);
}
console.log("主線程1")
awaitDemo();
Promise.resolve(123).then(res => console.log(res))
console.log("主線程2")
//主線程1
//主線程2
//111
//123

改變的是sleep函數(shù)里面的內(nèi)容,以及awaitDemo與后面代碼的執(zhí)順序

我來說一下區(qū)別:執(zhí)行sleep的時(shí)候遇到Promise.resolve(111); 進(jìn)入微隊(duì)列1,再執(zhí)行Promise.resolve(123).then(res => console.log(res)) 在進(jìn)入微隊(duì)列2,所以主隊(duì)列宏任務(wù)執(zhí)行完畢后執(zhí)行微隊(duì)列按照順序應(yīng)該是執(zhí)行微隊(duì)列打印111,再打印222

再來一個(gè)小的變化:


function sleep(second) {
    return Promise.resolve(111).then(res => res);
}
async function awaitDemo() {
    let result = await sleep(0);
    console.log(result?result:0);
}
console.log("主線程1")
awaitDemo();
Promise.resolve(123).then(res => console.log(res))
console.log("主線程2")

// 主線程1
//主線程2
//123
//111

這次的改變僅僅是sleep函數(shù)里面的內(nèi)容,resolve以后在then函數(shù)里面返回了res值,

注意:then方法會(huì)返回一個(gè)新的Promise實(shí)例。因此可以采用鏈?zhǔn)綄懛ǎ瑃hen方法中的回調(diào)函數(shù)(不管是成功的回調(diào)還是失敗的回調(diào))返回了一個(gè)參數(shù)(return xxx),那么這個(gè)then方法返回的新的promise的狀態(tài)會(huì)變成fulfilled

為什么這次改變以后打印111會(huì)在打印222的后面呢,大家細(xì)想一下就知道了,resolve(111).then 進(jìn)入了第一個(gè)微任務(wù),這個(gè)微任務(wù)在執(zhí)行的時(shí)候又返回一個(gè)promise(then(res => res)),這個(gè)又是一個(gè)微任務(wù)記做3,這個(gè)時(shí)候不會(huì)立馬執(zhí)行,等待微任務(wù)2執(zhí)行完畢才能執(zhí)行,所以打印的順序又不同了,大家可以細(xì)細(xì)理解下這里

上面講解了await等待的是一個(gè)Promise的情況,下面講解如果等待的不是一個(gè)Promise會(huì)是怎么樣呢?

如果 await 的不是一個(gè) promise ,而是一個(gè)表達(dá)式。await 會(huì)暫停 async 函數(shù)內(nèi)后面的代碼執(zhí)行,先執(zhí)行 async 函數(shù)外的同步代碼(注意,此時(shí)會(huì)先執(zhí)行完 await 后面的表達(dá)式后再執(zhí)行 async 函數(shù)外的同步代碼)

我的個(gè)人理解這種情況下await后面的代碼或者表達(dá)式只要遇到return ,或者運(yùn)行結(jié)束的時(shí)候(函數(shù)中不指定返回值,則會(huì)默認(rèn)return返回undefined)await以后的代碼可以看成是一個(gè)微隊(duì)列來執(zhí)行,等待下一個(gè)微隊(duì)列執(zhí)行的時(shí)機(jī)再執(zhí)行await后面的代碼

再來看一個(gè)例子:

function sleep(second) {
    return 100;
}
async function awaitDemo() {
    let result = await sleep(0);
    console.log(result? result:0);
}
console.log("主線程1")
setTimeout(() => {
            console.log('sleep');
}, 0);
Promise.resolve(123).then(res => console.log(res))
awaitDemo();
console.log("主線程2")

//主線程1
//主線程2
//123
//100
//sleep

執(zhí)行順序是這樣的:

  1. 先打印的是主任務(wù)的宏隊(duì)列 ”主線程1” ,

  2. 然后遇到setTimeout函數(shù)放入下一個(gè)宏隊(duì)列,

  3. 然后遇到 Promise.resolve(123).then 放入到改宏隊(duì)列對應(yīng)的微隊(duì)列里面然后執(zhí)行await sleep(0);

  4. 進(jìn)入到sleep函數(shù),函數(shù)里面return 100返回,這個(gè)時(shí)候return 100 這句話變成如下:


Promise.resolve(100);

await以后的代碼可以看成是一個(gè)微隊(duì)列來執(zhí)行(這個(gè)是我個(gè)人比較深刻的理解,很多網(wǎng)絡(luò)上面的文章其實(shí)都沒說清楚這一塊) ,await函數(shù)必須等待Promise返回fullfilled狀態(tài)才會(huì) 往下執(zhí)行(可以看上面的解釋),所以接下來不會(huì)執(zhí)行await后面的代碼,微任務(wù)的代碼不會(huì)立馬執(zhí)行,轉(zhuǎn)而繼續(xù)執(zhí)行宏任務(wù)的代碼

  1. 然后執(zhí)行主任務(wù)宏隊(duì)列的打印”主線程2” ,然后執(zhí)行微隊(duì)列任務(wù)打印”123” , 執(zhí)行下一個(gè)微隊(duì)列任務(wù)return 100 返回給await函數(shù)執(zhí)行下面的打印“100”,

  2. 然后執(zhí)行另一個(gè)宏任務(wù)打印“sleep”

好了上面的幾個(gè)例子已經(jīng)比較好的說明了《promise await 宏任務(wù) 微任務(wù)》這個(gè)主題的內(nèi)容了,如果你還沒明白的建議你多看幾遍,有什么問題可以給我留言,這個(gè)只是為了給接下來flutter的內(nèi)容做一個(gè)對比的鋪墊,希望大家繼續(xù)關(guān)注,謝謝···

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容