有道題,得細說

念念不忘,必有迴響(性格嚴肅的人跳過這一段,皮百萬的不用)

最近,在一次正式場合下,遇到了一道檢驗js相關原理的題目,當時虎軀一震,這不是送分兒咩?不禁分說,大筆一揮,寫完以後還驕傲的叉了會兒腰,大概是這樣事兒的:promise

(膨脹使我頭大)bash

完事兒以後,彷佛略有不妥,可是做爲快樂風男,前進的道路上毫不回頭,縱使身後洪水滔天。 然而緣分就是這麼巧,一個非正式場合下,再次相遇,怎麼能放過人前顯聖的機會(讀書人裝逼不叫裝逼,叫人前顯聖)?一頓鍵盤後,準備再叉會兒腰,然而正確答案讓我猝不及防。。。

看看這道小可愛

async function async1() {
    console.log(1)
    const result = await async2();
    console.log(3)
}

async function async2() {
    console.log(2);
}

Promise.resolve().then(() => {
    console.log(4)
})

setTimeout(() => {
    console.log(5)
})

async1();
console.log(6);
複製代碼

請寫出打印結果。 對於相關原理不太瞭解的同窗,想來是要翻車的;有點了解的同窗,指定會是一副激動的心、顫抖的手,插着腰,敲出162345。 然而正確答案:異步

結果是意外的,這波兒腰就先別插了,我們象徵性的分析分析,這是啥緣由。

分析

根據這道題的呈現,能夠看出,涉及到的基本原理有如下幾個方面:async

  1. promise原理
  2. async-await原理
  3. 同步異步
  4. 宏任務微任務原理

每個方面其實都包含很多的知識,在這裏就不一一細講了,畢竟網上處處都是(不是不想講,也不是不會講,只是以前有同窗評論我,都9102年了,再寫這種基礎活該捱罵。我這向來都是從善如流,虛心聽取)。因此我們只講涉及到的。函數

首先,同步異步就很常見了,一筆帶過,同步,從上到下,從左到右,按順序執行code;異步,code執行到該行爲時,先收集起來,暫不執行,等到執行時機到來,在執行隊列裏收集到的行爲。工具

而後,宏任務微任務,簡單來講,均屬異步行爲,通常狀況下,一個宏任務裏面老是先順序執行同步代碼,再順序執行該宏任務中的微任務(嵌套的話,會更復雜一些),等到都執行完畢,再進入下一個宏任務。啥是宏任務?script標籤包含的code、setTimeout、setInterval、setImmediately、I/O等。啥是微任務?promise.then、process.nextTick等。學習

接着,promise,一個處理異步行爲的工具,屬於微任務,例題中相關代碼爲:ui

Promise.resolve().then(() => {
    console.log(4)
});
複製代碼

怎麼理解這段代碼? Promise.resolve()返回了一個promise對象(也叫thenable對象),而且這個對象立馬被resolve。 可是因爲resolve函數裏面的code是一個異步的行爲,因此儘管resolve是在then以前執行,可是,裏面的異步行爲是排在then執行以後才觸發。(異步行爲=》執行當前promise實例中存放then方法收集到的函數隊列,這個隊列是一個微任務隊列), 而後這個對象的then方法收集了一個回調函數,放在promise實例的微任務回調隊列裏(then只是收集,並無執行,是resolve的執行,才觸發了微任務異步隊列的執行),then會返回一個新的promise實例,可是這個這裏不涉及,暫且不表。spa

最後,async-await,這道題裏涉及到兩個很關鍵的概念:code

2. await 只能在 async 函數中使用。 await 後面能夠跟普通的函數,也能夠跟帶有then方法的對象,也就是thenable。若是後面跟的是thenable時,await會收集thenable對象的原型對象上的then方法,並給其注入resolve和reject;而後阻塞當前做用域代碼的執行,等待注入的resolve開啓微任務異步隊列的執行。若是後面不是thenable對象的話,直接開啓微任務異步隊列的執行。(此處感謝@茹挺進大佬的審查和建議) 執行這段代碼,理解上述說明:

var o = {};
o.__proto__.then = function(resolve,reject){
        resolve(1);
    };

(async ()=>{
    var r = await o;
    console.log(r);
})();
複製代碼

注意:thenable對象中被注入的resolve函數,若是不執行,那麼await將一直阻塞,當前做用域裏,await後面的代碼永遠不會執行。

用分析結果執行代碼

  1. 聲明瞭async1,
  2. 聲明瞭async2,
  3. Promise.resolve()返回了一個promise對象,而且這個對象立馬被resolve 而後這個這個對象的then方法收集了一個回調函數,放在promise實例的微任務回調隊列裏。 因此此時,當前宏任務隊列裏的微任務隊列裏,只有一個promise的隊列,裏面有一個打印4的回調。
  4. 遇到了setTimeout,回調直接被置入下一個宏任務隊列。
  5. 執行async1,打印1, 而後執行async2,打印2, 可是此時遇到了await,await作了兩件事,1.返回了async1的函數,2。阻塞了async2中await後面的函數,先開啓當前微任務異步隊列的執行。
  6. await返回後,執行後面的同步代碼,打印6,此時同步的代碼執行完畢。
  7. 同步的代碼執行完畢後,執行剛纔開啓的微任務異步隊列,打印4,此時await開啓的微任務異步隊列執行完畢。
  8. await開啓的微任務異步隊列執行完畢後,接觸阻塞,打印3。
  9. 當前宏任務打印完畢,執行下一個宏任務,打印5.

對await的懷疑

await會如咱們分析的這樣去作麼?它會和promise的微任務隊列這樣配合? 咱們直接寫一個例子試一下:

async function async1() {
    console.log(1)
    const result = await async2();
    console.log(3)
}

async function async2() {
    console.log(2);
    return {
        then:(res)=>{
            console.log(7);
            res();
        }
    }
}

Promise.resolve().then(() => {
    console.log(4)
})

setTimeout(() => {
    console.log(5)
})

async1();
console.log(6)
複製代碼

根據咱們上面講的原理,結果應該是1,2,6,4,7,3,5。 你去打印試試吧,在谷歌裏哦,防止翻車~

寫在最後

須要聲明的一點是,我不是一個教授者,我只是一個分享者、一個討論者、一個學習者,有不一樣的意見或新的想法,提出來,咱們一塊兒研究。分享的同時,並不僅是被分享者在學習進步,分享者亦是。

知識遍地,拾到了就是你的。

既然有用,不妨點贊,讓更多的人瞭解、學習並提高。

相關文章
相關標籤/搜索