1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        成為自信的node.js開(kāi)發(fā)者(二)

        共 9579字,需瀏覽 20分鐘

         ·

        2021-06-03 18:16


        作者:霧豹

        原文地址:https://juejin.im/post/5c6a785951882562986cf126


        相關(guān)推薦閱讀:成為自信的node.js開(kāi)發(fā)者(一)


        這一章,我們來(lái)學(xué)習(xí)一下event_loop, 本文內(nèi)容旨在厘清瀏覽器(browsing context)和Node環(huán)境中不同的 Event Loop。

        首先清楚一點(diǎn):瀏覽器環(huán)境和 node環(huán)境的event-loop 完全不一樣。

        瀏覽器環(huán)境

        為了協(xié)調(diào)事件、用戶交互、腳本、UI渲染、網(wǎng)絡(luò)請(qǐng)求等行為,用戶引擎必須使用Event Loop。event loop包含兩類:基于browsing contexts,基于worker。

        本文討論的瀏覽器中的EL基于browsing contexts

        上面圖中,關(guān)鍵性的兩點(diǎn):

        同步任務(wù)直接進(jìn)入主執(zhí)行棧(call stack)中執(zhí)行

        等待主執(zhí)行棧中任務(wù)執(zhí)行完畢,由EL將異步任務(wù)推入主執(zhí)行棧中執(zhí)行

        task——宏任務(wù)

        task在網(wǎng)上也被成為macrotask (宏任務(wù))

        宏任務(wù)分類:

        script代碼

        setTimeout/setInterval

        setImmediate (未實(shí)現(xiàn))

        I/O

        UI交互

        宏任務(wù)特征

        一個(gè)event loop 中,有一個(gè)或多個(gè) task隊(duì)列。

        不同的task會(huì)放入不同的task隊(duì)列中:比如,瀏覽器會(huì)為鼠標(biāo)鍵盤事件分配一個(gè)task隊(duì)列,為其他的事件分配另外的隊(duì)列。

        先進(jìn)隊(duì)列的先被執(zhí)行

        microtask——微任務(wù)

        微任務(wù)

        微任務(wù)的分類

        通常下面幾種任務(wù)被認(rèn)為是microtask

        promise(promisethencatch才是microtask,本身其內(nèi)部的代碼并不是)

        MutationObserver

        process.nextTick(nodejs環(huán)境中)

        微任務(wù)特性

        一個(gè)EL中只有一個(gè)microtask隊(duì)列。

        event-loop的循環(huán)過(guò)程

        一個(gè)EL只要存在,就會(huì)不斷執(zhí)行下邊的步驟:

        先執(zhí)行同步代碼,所有微任務(wù),一個(gè)宏任務(wù),所有微任務(wù)(,更新渲染),一個(gè)宏任務(wù),所有微任務(wù)(,更新渲染)……
        執(zhí)行完microtask隊(duì)列里的任務(wù),有可能會(huì)渲染更新。在一幀以內(nèi)的多次dom變動(dòng)瀏覽器不會(huì)立即響應(yīng),而是會(huì)積攢變動(dòng)以最高60HZ的頻率更新視圖

        例子

        setTimeout(() => console.log('setTimeout1'), 0);
        setTimeout(() => {
            console.log('setTimeout2');
            Promise.resolve().then(() => {
                console.log('promise3');
                Promise.resolve().then(() => {
                    console.log('promise4');
                })
                console.log(5)
            })
            setTimeout(() => console.log('setTimeout4'), 0);
        }, 0);
        setTimeout(() => console.log('setTimeout3'), 0);
        Promise.resolve().then(() => {
            console.log('promise1');
        })

        打印出來(lái)的結(jié)果是 :

        promise1
        setTimeout1
        setTimeout2
        'promise3'
        5
        promise4
        setTimeout3
        setTimeout4

        另外一個(gè)例子:

        console.log('script start')

        async function async1({
            await async2()
            console.log('async1 end')
        }
        async function async2({
            console.log('async2 end')
        }
        async1()

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

        new Promise(resolve => {
            console.log('Promise')
            resolve()
        })
            .then(function ({
                console.log('promise1')
                setTimeout(() => {
                    console.log('sssss')
                }, 0)
            })
            .then(function ({
                console.log('promise2')
            })

        console.log('script end')

        在瀏覽器內(nèi)輸出結(jié)果如下, node內(nèi)輸出結(jié)果不同

        'script start'
        '
        async2 end'
        '
        Promise'
        '
        script end'
        '
        async1 end'
        '
        promise1'
        '
        promise2'
        '
        setTimeout'
        '
        sssss'
        1. await 只是 fn().then() 這些寫法的語(yǔ)法糖,相當(dāng)于 await 那一行代碼下面的代碼都被當(dāng)成一個(gè)微任務(wù),推入到了microtask queue

        2. 順序:執(zhí)行完同步任務(wù),執(zhí)行微任務(wù)隊(duì)列中的全部的微任務(wù),執(zhí)行一個(gè)宏任務(wù),執(zhí)行全部的微任務(wù)

        node 環(huán)境中

        Node中的event-looplibuv庫(kù) 實(shí)現(xiàn),js是單線程的,會(huì)把回調(diào)和任務(wù)交給libuv

        event loop 首先會(huì)在內(nèi)部維持多個(gè)事件隊(duì)列,比如 時(shí)間隊(duì)列、網(wǎng)絡(luò)隊(duì)列等等,而libuv會(huì)執(zhí)行一個(gè)相當(dāng)于 while true的無(wú)限循環(huán),不斷的檢查各個(gè)事件隊(duì)列上面是否有需要處理的pending狀態(tài)事件,如果有則按順序去觸發(fā)隊(duì)列里面保存的事件,同時(shí)由于libuv的事件循環(huán)每次只會(huì)執(zhí)行一個(gè)回調(diào),從而避免了 競(jìng)爭(zhēng)的發(fā)生

        個(gè)人理解,它與瀏覽器中的輪詢機(jī)制(一個(gè)task,所有microtasks;一個(gè)task,所有microtasks…)最大的不同是,node輪詢有phase(階段)的概念,不同的任務(wù)在不同階段執(zhí)行,進(jìn)入下一階段之前執(zhí)行所有的process.nextTick() 和 所有的microtasks。

        階段

        timers階段

        在這個(gè)階段檢查是否有超時(shí)的timer(setTimeout/setInterval),有的話就執(zhí)行他們的回調(diào)

        timer設(shè)定的閾值不是執(zhí)行回調(diào)的確切時(shí)間(只是最短的間隔時(shí)間),node內(nèi)核調(diào)度機(jī)制和其他的回調(diào)函數(shù)會(huì)推遲它的執(zhí)行

        由poll階段來(lái)控制什么時(shí)候執(zhí)行timers callbacks

        I/O callback 階段

        處理異步事件的回調(diào),比如網(wǎng)絡(luò)I/O,比如文件讀取I/O,當(dāng)這些事件報(bào)錯(cuò)的時(shí)候,會(huì)在 `I/O` callback階段執(zhí)行

        poll 階段

        這里是最重要的階段,poll階段主要的兩個(gè)功能:

           處理poll queue的callbacks

           回到timers phase執(zhí)行timers callbacks(當(dāng)?shù)竭_(dá)timers指定的時(shí)間時(shí))


        進(jìn)入poll階段,timer的設(shè)定有下面兩種情況:

        1.  event loop進(jìn)入了poll階段, **未設(shè)定timer**

               poll queue不為空:event loop將同步的執(zhí)行queue里的callback,直到清空或執(zhí)行的callback到達(dá)系統(tǒng)上限

               poll queue為空

                   如果有設(shè)定` callback`, event loop將結(jié)束poll階段進(jìn)入check階段,并執(zhí)行check queue (check queue是 setImmediate設(shè)定的)

                   如果代碼沒(méi)有設(shè)定setImmediate() callback,event loop將阻塞在該階段等待callbacks加入poll queue

        2.  event loop進(jìn)入了 poll階段, **設(shè)定了timer**

               如果poll進(jìn)入空閑狀態(tài),event loop將檢查timers,如果有1個(gè)或多個(gè)timers時(shí)間時(shí)間已經(jīng)到達(dá),event loop將回到 timers 階段執(zhí)行timers queue


        這里的邏輯比較復(fù)雜,流程可以借助下面的圖進(jìn)行理解:

        ![](https://ws1.sinaimg.cn/large/006tKfTcgy1g0anodoa11j311i0h0t8w.jpg)

        check 階段

        一旦poll隊(duì)列閑置下來(lái)或者是代碼被`setImmediate`調(diào)度,EL會(huì)馬上進(jìn)入check phase

        close callbacks

        關(guān)閉I/O的動(dòng)作,比如文件描述符的關(guān)閉,連接斷開(kāi)等

        如果socket突然中斷,close事件會(huì)在這個(gè)階段被觸發(fā)

        同步的任務(wù)執(zhí)行完,先執(zhí)行完全部的process.nextTick() 和 全部的微任務(wù)隊(duì)列,然后執(zhí)行每一個(gè)階段,每個(gè)階段執(zhí)行完畢后,

        注意點(diǎn)

        setTimeout 和 setImmediate

        1. 調(diào)用階段不一樣

        2. 不同的io中,執(zhí)行順序不保證

        二者非常相似,區(qū)別主要在于調(diào)用時(shí)機(jī)不同。

        setImmediate 設(shè)計(jì)在poll階段完成時(shí)執(zhí)行,即check段;

        setTimeout 設(shè)計(jì)在poll階段為空閑時(shí),且設(shè)定時(shí)間到達(dá)后執(zhí)行,但它在timer階段執(zhí)行

        setTimeout(function timeout ({
          console.log('timeout');
        },0);
        setImmediate(function immediate ({
          console.log('immediate');
        });

        對(duì)于以上代碼來(lái)說(shuō),setTimeout 可能執(zhí)行在前,也可能執(zhí)行在后。
        首先 setTimeout(fn, 0) === setTimeout(fn, 1),這是由源碼決定的。

        如果在準(zhǔn)備時(shí)候花費(fèi)了大于 1ms 的時(shí)間,那么在 timer 階段就會(huì)直接執(zhí)行 setTimeout 回調(diào)。
        如果準(zhǔn)備時(shí)間花費(fèi)小于 1ms,那么就是 setImmediate 回調(diào)先執(zhí)行了。

        也就是說(shuō),進(jìn)入事件循環(huán)也是需要成本的。有可能進(jìn)入event loop 時(shí),setTimeout(fn, 1) 還在等待timer中,并沒(méi)有被推入到 time 事件隊(duì)列,而setImmediate 方法已經(jīng)被推入到了 check事件隊(duì)列 中了。那么event_loop 按照time、i/opoll、check、close 順序執(zhí)行,先執(zhí)行immediate 任務(wù)。

        也有可能,進(jìn)入event loop 時(shí),setTimeout(fn, 1) 已經(jīng)結(jié)束了等待,被推到了time 階段的隊(duì)列中,如下圖所示,則先執(zhí)行了timeout 方法。

        所以,setTimeout setImmediate 哪個(gè)先執(zhí)行,這主要取決于,進(jìn)入event loop 花了多長(zhǎng)時(shí)間。

        但當(dāng)二者在異步i/o callback內(nèi)部調(diào)用時(shí),總是先執(zhí)行setImmediate,再執(zhí)行setTimeout

        const fs = require('fs')
        fs.readFile(__filename, () => {
            setTimeout(() => {
                console.log('timeout');
            }, 0)
            setImmediate(() => {
                console.log('immediate')
            })
        })

        在上述代碼中,setImmediate 永遠(yuǎn)先執(zhí)行。因?yàn)閮蓚€(gè)代碼寫在 IO 回調(diào)中,IO 回調(diào)是在 poll 階段執(zhí)行,當(dāng)回調(diào)執(zhí)行完畢后隊(duì)列為空,發(fā)現(xiàn)存在 setImmediate 回調(diào),所以就直接跳轉(zhuǎn)到 check 階段去執(zhí)行回調(diào)了。

        process.nextTick() 和 setImmediate()

        官方推薦使用 setImmediate(),因?yàn)楦菀淄评恚布嫒莞嗟沫h(huán)境,例如瀏覽器環(huán)境

        process.nextTick() 在當(dāng)前循環(huán)階段結(jié)束之前觸發(fā)

        setImmediate() 在下一個(gè)事件循環(huán)中的check階段觸發(fā)

        通過(guò)process.nextTick()觸發(fā)的回調(diào)也會(huì)在進(jìn)入下一階段前被執(zhí)行結(jié)束,這會(huì)允許用戶遞歸調(diào)用 process.nextTick() 造成I/O被榨干,使EL不能進(jìn)入poll階段

        因此node作者推薦我們盡量使用setImmediate,因?yàn)樗辉赾heck階段執(zhí)行,不至于導(dǎo)致其他異步回調(diào)無(wú)法被執(zhí)行到

        例子

        console.log('start')
        setTimeout(() => {
          console.log('timer1')
          Promise.resolve().then(function({
            console.log('promise1')
          })
        }, 0)
        setTimeout(() => {
          console.log('timer2')
          Promise.resolve().then(function({
            console.log('promise2')
          })
        }, 0)
        Promise.resolve().then(function({
          console.log('promise3')
        })
        console.log('end')

        注意:主棧執(zhí)行完了之后,會(huì)先清空 process.nextick() 隊(duì)列和microtask隊(duì)列中的任務(wù),然后按照每一個(gè)階段來(lái)執(zhí)行先處理異步事件的回調(diào),比如網(wǎng)絡(luò)I/O,比如文件讀取I/O。當(dāng)這些I/O動(dòng)作都結(jié)束的時(shí)候,在這個(gè)階段會(huì)觸發(fā)它們的

        另外一個(gè)例子

        const {readFile} = require('fs')

        setTimeout(() => {
            console.log('1')
        }, 0)

        setTimeout(() => {
            console.log('2')
        }, 100)

        setTimeout(() => {
            console.log('3')
        }, 200)

        readFile('./test.js', () => {
            console.log('4')
        })

        readFile(__filename, () => {
            console.log('5')
        })

        setImmediate(() => {
            console.log('立即回調(diào)')
        })

        process.nextTick(() => {
            console.log('process.nexttick的回調(diào)')
        })

        Promise.resolve().then(() => {

            process.nextTick(() => {
                console.log('nexttick 第二次回調(diào)')
            })
            console.log('6')
        }).then(() => {
            console.log('7')
        })

        上面代碼的結(jié)果是:

        process.nexttick的回調(diào)
        6
        7
        nexttick 第二次回調(diào)
        1
        立即回調(diào)
        4
        5
        2
        3

        上面代碼需要注意點(diǎn):

        1. 下面兩個(gè)回調(diào)任務(wù),要等100ms200ms 才能被推入到timers 階段的任務(wù)隊(duì)列

        2. 兩個(gè)讀取文件的回調(diào),需要等待讀取完成后,才能被推入到 poll 階段的任務(wù)隊(duì)列。(不是被推入到 io 階段的任務(wù)隊(duì)列,只有讀取失敗等異常的回調(diào),才會(huì)被推入到 io 階段的任務(wù)隊(duì)列)

        3. 在微任務(wù)里面,新添加的process.nextTick() 也會(huì)在新階段的開(kāi)始之前被執(zhí)行。簡(jiǎn)單理解為,在每一個(gè)階段的任務(wù)隊(duì)列開(kāi)始之前,都需要全部清空process.nextTickmicrotask 任務(wù)隊(duì)列

        一個(gè)誤區(qū)

        自己在驗(yàn)證上面的想法的時(shí)候,實(shí)驗(yàn)過(guò)很多代碼,從未失手過(guò),但是當(dāng)實(shí)驗(yàn)到下面的代碼時(shí):

        Promise.resolve().then(() => {
            console.log(1)
            Promise.resolve().then(() => {
                console.log(2)
            })
        }).then(() => {
            console.log(3)
        })

        按照上面我們講的,這里應(yīng)該是輸出132, 但是反復(fù)驗(yàn)證,在 node 實(shí)際輸出的是 123,連續(xù)好幾天都不得其解,后來(lái)看到一個(gè)問(wèn)答,才恍然大悟:https://stackoverflow.com/questions/36870467/what-is-the-order-of-execution-in-javascript-promises

        首先,上面的代碼,在.then() 的回調(diào)函數(shù)中去執(zhí)行promise.resolve(), 實(shí)際上是, 在目前的promise 鏈中新建了一個(gè)獨(dú)立的 promise鏈 。你沒(méi)有任何辦法保證這兩個(gè)哪個(gè)先執(zhí)行完,這實(shí)際上是node引擎 的一個(gè)bug,就像一口氣發(fā)出兩個(gè)請(qǐng)求,并不知道哪個(gè)請(qǐng)求先返回。

        每次我們都能得到相同的結(jié)果是因?yàn)?,我?code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">Promise.resolve()里面恰好沒(méi)有異步的操作,這并不是event-loop 專門設(shè)計(jì)成這樣的。

        所以,不必花太多的時(shí)間,在上面的代碼中,實(shí)際寫代碼中,也不會(huì)出現(xiàn)這種情況。





        瀏覽 58
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91成人无码看片在线观看网址 | 欧美一级婬片免费视频日日木 | 麻豆网站免费在线观看 | 成人精品水蜜桃 | 黄色小说五月天 | 成人精品无码四虎 | 女主播福利视频 | 黄色动漫免费视频 | 日韩免费一区二区三区 | 欧美乱婬妺妺躁爽A片 |