1. 程式人生 > >淺析JS異步執行機制

淺析JS異步執行機制

一個隊列 http請求 調度 等待 __name__ 服務端 nco sta req

前言

JS異步執行機制具有非常重要的地位,尤其體現在回調函數和事件等方面。本文將針對JS異步執行機制進行一個簡單的分析。

從一份代碼講起

下面是兩個經典的JS定時執行函數,這兩個函數的區別相信對JS有一定基礎的同學是十分清楚的。timeout僅僅只會執行一次,而interval則會執行多次。

    setTimeout(function (args) {
        console.log(‘timeout‘)
    }, 1000);
    setInterval(function (args) {
        console.log(‘interval‘)
    }, 1000);

那麽再看一份代碼

    setTimeout(function (args) {
        console.log(‘timeout‘);
        setTimeout(arguments.callee, 1000);
    }, 1000);
    setInterval(function (args) {
        console.log(‘interval‘)
    }, 1000);

這兩份代碼是否存在區別呢?在setTimeout中遞歸調用貌似和setInterval一樣,但是實際上由於JS異步執行機制的問題,導致這兩個函數存在著一定的差異。

如何理解JS異步執行機制

JS是單線程程序,從而避免了並發訪問的一系列問題。但也正是由於單線程這樣一個機制,導致JS的異步執行並不能按照傳統的多線程方式進行異步執行,所有的異步時間要插入到同一個隊列中,依次在主線程中執行。
技術分享圖片


這裏有一張圖片,可以比較好的解釋JS的異步執行機制。
在瀏覽器中,一般情況下會存在三個線程,JS執行引擎,HTTP線程,事件觸發線程。但是需要註意的是,所有的JS核心邏輯都需要在JS執行引擎線程中執行。
例如我們可以使用下面這樣一段代碼發送AJAX請求

    var xmlReq = createXMLHTTP();//創建一個xmlhttprequest對象
    function testAsynRequest() {
        var url = "http://127.0.0.1:5000/";
        xmlReq.open("post", url, true);
        xmlReq
.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlReq.onreadystatechange = function () { if (xmlReq.readyState == 4) { if (xmlReq.status == 200) { var jsonData = eval(‘(‘ + xmlReq.responseText + ‘)‘); alert(jsonData.message); } else if (xmlReq.status == 404) { alert("Requested URL is not found."); } else if (xmlReq.status == 403) { alert("Access denied."); } else { alert("status is " + xmlReq.status); } } }; xmlReq.send(null); } testAsynRequest();//1秒後調用回調函數 while (true) { }

服務端代碼

from flask import Flask

app = Flask(__name__)

@app.route(‘/‘, methods=[‘POST‘, ‘GET‘])
def print():
    return ‘hello world‘
if __name__ == ‘__main__‘:
    app.run()

這段代碼是否會輸出hello world呢?經過測驗,發現並不會輸出HelloWorld,瀏覽器會進入假死狀態。造成這種情況的原因正是JS異步回調單線程的運行機制。在發送HTTP請求以後,HTTP請求會啟動一個線程進行發送,收到響應以後會,事件觸發線程會將響應事件加入到等待隊列中,等待JS引擎空閑後執行。
但是由於while(true)導致JS引擎永遠不存在空閑,從而導致響應事件一致無法觸發。

重新思考

通過一個簡單的AJAX DEMO,可以簡單了解了JS時間執行的一個流程。那麽針對上面的那張圖片,和最開始提出的settimeout的問題,JS又是如何調度和處理的呢?
JS在定時器函數初始化以後就會開始執行定時任務,到達時間之後如果此時JS引擎空閑,則會直接執行定時任務,否則會將定時任務加入到等待隊列中。
對於加入到等待隊列中的任務來說,會在JS引擎空閑的時候再不斷進行執行。因此如果此時引擎並非空閑,那麽setTimeout會等待一段時間後才能執行。
對於setInterval來說,也是需要加入到等待隊列中的,但是setInterval並不會因為加入到等待隊列中而停止計時,此時如果到了第二個Interval,而第一個Interval還沒有開始執行,那麽此時隊列中舊有存在兩個Interval可能,如果這樣累加下去,那麽就可能會陷入大量Interval的累加,造成線程嚴重阻塞的問題,因此JS引擎做了一個輕度的優化,如果隊列中有Interval,那麽這個Interval不會加入隊列。但是如果Interval已經pop出隊列開始執行,那麽Interval將會加入隊列。
針對上面的分析,我們可以得出一個結論,相比於setTimeout函數遞歸調用,在JS中由於單線程的異步執行機制,setInterval執行的頻率會更高。因為setTimeout在執行完成以後才會開始下一輪定時任務,但是setInterval是持續執行定時任務,尤其是在setTimeout裏的任務執行時間較長的時候,setInterval和setTimeout會有比較明顯的頻率差異。

淺析JS異步執行機制