天天看點

JavaScript異步初探

異步程式設計的核心

程式中現在運作的部分和将來運作的部分之間的關系。

異步概念

同步的Ajax

var data=ajax(url);
console.log(data)
           

任何時候都不應該使用同步的方式,因為它會鎖定浏覽器的UI線程

将來執行的塊

任何時候隻要把一段代碼包裝成一個函數,并指定它在響應某個事件時執行,你就建立了一個将來執行的塊,這就是異步。

事件循環

JS引擎運作在宿主環境中,比如浏覽器,比如OS(NODE),他們提供了一種機制來處理程式中多個塊的執行,且執行每塊時調用JS引擎,這種機制被稱為事件循環。

原因

JS引擎本身隻是一個按需執行JS任意代碼片段的環境,而事件的排程是交給了包含它的環境進行,比如浏覽器,Node。

EventLoop

//僅僅是簡化的代碼。
var eventLoop=[];
var event;
//永遠執行
while(){
    //一次tick
    if(eventLoop.length>){
        //拿到隊列中的下一個事件
        event=eventLoop.shift();
        //現在執行下一個事件
        try{
            event();
        }catch(err){
            reportError(err);
        }
    }
}
           

可以看到,我們的代碼塊實際上是暫存到了一個隊列中,然後FIFO執行它。

原理

循環的每一輪我們稱為TICK

對每個TICK來說,如果隊列中有等待事件,那麼就會從隊列中摘下

一個事件并執行。即你的回調函數被執行一個。

setTimeout和事件循環。

setTimeout并沒有把你的回調函數挂在事件循環隊列中。它做的是設定一個定時器。

當定時器到時後,環境*會把你的回調函數放在事件循環中* ,然後未來某個時刻執行這個回調!

是以setTimeout才會不準确! 精度不高!!! 它要根據事件隊列的狀态來定!!!

異步和并行

度程序和線程獨立運作,并可能同時運作在不同的處理器或不同的計算機上,但是多個線程能夠共享單個程序的記憶體。

異步:但是事件循環把自身的工作分成一個個任務順序執行,

不允許對共享記憶體的并行通路和修改~

共存

通過分立線程中彼此合作的事件循環。

其實并行和異步是可以同時存在的,

因為并行線程的交替執行和異步事件的交替排程,粒度是完全不同的!

多線程

盡管在JS中是沒有多線程的。 我們還是看看如果并行運作下面這段程式會發生什麼事情?

function later(){
    answer=answer*;
    console.log("Meaning of life:",answer);
}
           

盡管later()的所有内容被看作一個單獨的事件循環隊列項,

也就是說我們在異步的思想來看,它是一個整體的單元。

但是如果以多線程的思想來看,這裡的每一語句,還必須細分。

比如:

answer=answer*2;

過程

需要先加載answer的值,然後把2放到某處

并執行乘法,取值,然後存到answer。

(作業系統是不斷取指執行的) 是以說多線程要細分到一個原子

操作(不會被線程排程機制打斷的操作)。

不妨做個假設

var a=;
function foo(){
    a=a+;
    //實際為
    //把a的值加載到X(記憶體位址)
    //把1儲存到Y(記憶體位址)
    //執行X+Y,結果存在X
    //把X的值儲存在a
}

function bar(){
    a=a*;
    //實際為
    //把a的值加載到X
    //把2的值存到Y
    //執行X乘以Y,存到X
    //把X的值儲存在a
}
foo();
bar();
           

如果是有兩個線程在運作,foo在bar之前或者bar在foo之前運作,不光如此,它們兩個函數中的原子操作也可能交替執行,它會得到不确定的結果。是以是非常複雜的。

總的來說

由于JS單線程的原因,是以foo()和bar(),也就是函數塊的代碼具有原子性,也就是說它們執行的時候不會被其他事情或任務打斷。同步執行沒什麼好說,它是現在運作,結果是确定的。

對于異步,将來執行的,運作結果則不确定。

并發

浏覽器端可能會觸發很多事件,這些事件被觸發然後會執行若幹個回調函數,這些任務同時執行就出現了并發,不管組成他們的單個運算是否并行執行。我們可以把并發看作“任務級” 與運算級并行(不同于處理器上的線程)不同。

處理方式:

可以用事件循環來處理并發,把很多事件的回調函數存入隊列。

競态(race condition)

同一段代碼有兩個可能的輸出,這種函數順序的不确定性就是競态.

非互動和互動

如果程序間沒有互相影響的話,不确定性是可以接受的。

它的順序不影響各自的結果,但如果他們是互相有依賴關系的。

比如

var res=[];
function response(data){
//..
}
//
ajax(url1,response);
ajax(url2,response);
           

這裡的順序是有依賴關系的,對res的結果有影響,執行的順序有影響!

我們可以簡單的協調:

function response(data){
    if(data.url===url1){
        res[]=data;
    }
    else {
        res[]=data;
    }
}
           

這就避免了競态。

協作

并發協作。 不再是通過共享作用域中的值進行互動。

取得一個運作時間過長的任務,将其分割成多個步驟或多批任務來處理 。

使得其他并發的任務有機會将自己的運算插入到事件循環隊列中交替執行。

比如:

function respon(data){
    var chunk=data.splice(,);
    //添加到res組
    res=res.concat();
}
           

把資料放在一個最多1000條項的塊中,這樣就確定了任務運作時間短。 事件循環隊列的交替運作會提高性能。但我們并沒有協調這些任務。是以結果的順序仍然不可預測!

任務和事件循環

任務隊列: 挂在事件循環隊列的每個TICK之後的一個隊列。

事件循環每個TICK中,可能出現的異步動作不會導緻一個完整

的新事件添加到事件循環隊列中,而會在目前TICK的任務隊列末尾

添加一個項目。

任務處理是在目前事件循環TICK結尾處。

繼續閱讀