從JavaScript的事件循環到Promise

JS線程是單線程運行機制,就是本身按順序作本身的事,瀏覽器線程用於交互和控制,JS能夠操做DOM元素,javascript

提及JS中的異步時,咱們須要注意的是,JS中其實有兩種異步,一種是基於瀏覽器的異步IO,好比Ajax,另一種是基於計時方法setTimeout和setInterval的異步。java

對於異步IO,好比ajax,寫代碼的時候都是順序執行的,可是在真正處理請求的時候,有一個單獨的瀏覽器線程來處理,而且在處理完成後會觸發回調。這種狀況下,參與異步過程的其實有2個線程主體,一個是javascript的主線程,另外一個是瀏覽器線程。es6

熟悉Javascript的人都知道,Javascript內部有一個事件隊列,全部的處理方法都在這個隊列中,Javascript主線程就是輪詢這個隊列處理,這個好處是使得CPU永遠是忙碌狀態。這個機制和Windows中的消息泵是同樣的,都是靠消息(事件)驅動,ajax

對於setTimeout和setInterval來講,當js線程執行到該代碼片斷時,js主線程會根據所設定的時間,當設定的時間到期時,將設置的回調函數放到事件隊列中,而後js主線程繼續去執行下邊的代碼,當js線程執行完主線程上的代碼以後,會去循環執行事件隊列中的函數。至於setTimeout或者setInterval設定的執行時間在實際表現時會有一些誤差,廣泛的一個解釋爲,當定時任務的時間到期時,本應該去執行該回調函數,可是這時js主線程可能還有任務在執行,或者是該回調函數再事件隊列中排的比較靠後,就致使該回調函數執行時間與定時器設定時間不一致。編程

那麼問題來了,什麼是事件隊列?數組

eventloop是一個用做隊列的數組,eventloop是一個一直在循環執行的,循環的每一輪成爲一個tick,在每個tick中,若是隊列中有等待事件,那麼就會從隊列中摘取下一個事件進行執行,這些事件就是咱們以前的回調函數。如今ES6精確指定了事件循環的工做細節,這意味着在技術上將其歸入了JavaScript引擎的勢力範圍,而不僅是由宿主環境決定了,主要的一個緣由是ES6中promise的引入。promise

var eventloop = []
var event;
while(true){
    if(eventloop.length>0){
        //拿到隊列中的下一個事件
        event = eventloop.shift();
        //如今執行下一個事件
        try{
            event();
        }catch(e){
            reportError(e);
        }
    }
}

在瀏覽器端,setTimeout中的最小時間間隔是W3C在HTML標準中規定,規定要求低於4ms的時間間隔算爲4ms。瀏覽器

任什麼時候候,只要把一個代碼包裝成一個函數,並指定它在響應某個事件時執行,你就是在代碼中建立了一個未來執行的模塊,也由此在這個程序中引入了異步機制。服務器

js引擎並非獨立運行的,它運行在宿主環境中,就是咱們所看到的Web瀏覽器,固然,隨着js的發展,包括最近的Node,即是給js提供了一個在服務器端運行的環境。而且如今的js還嵌入到了機器人到電燈泡的各類各樣的設配中。異步

可是這些全部的環境都有一個共同的「點」,即爲都提供了一種機制來處理程序中的多個塊的執行,且執行每一個塊時調用JavaScript引擎,這種機制被稱爲事件循環。

js引擎自己並無時間的概念,只是一個按須要執行JavaScript任意代碼片斷的環境。

對於js中的回調,咱們最多見的就是鏈式回調和嵌套回調

咱們常常再ajax中嵌套ajax調用而後再嵌套ajax調用,這就是回調地獄,回調最大的問題就是控制反轉,它會致使信任鏈的徹底斷裂,爲了解決回調中的控制反轉問題,有些API提供了分離回調(一個用於成功通知,一個用於失敗通知),例如ajax中的success函數和failure函數,這種狀況下,API的出錯處理函數failure()經常是能夠省略的,若是沒有提供的話,就是假定這個錯誤能夠吞掉。

還有一種回調模式叫作「error-first"風格,其中回調的第一個參數保留用做錯誤對象,若是成功的話,這個參數就會被清空/置假。

回調函數是JavaScript異步的基礎單元,可是隨着JavaScript愈來愈成熟,對於異步領域的發展,回調已經不夠用了。

Promise

Promise 是異步編程中的一種解決方案,最先由社區提出和實現,ES6將其寫進了語言標準,統一了用法,原生提供了Promise對象。

Promise是一種封裝和組合將來值的易於複用的機制。一種在異步任務中做爲兩個或更多步驟的流程控制機制,時序上的this-then-that. 假定調用一個函數foo(),咱們並不須要去關心foo中的更多細節,這個函數可能當即完成任務,也可能過一段時間纔去完成。對於咱們來說,咱們只須要知道foo()何時完成任務,這樣咱們就能夠去繼續執行下一個任務了,在傳統的方法中,咱們回去選擇監聽這個函數的完成度,當它完成時,經過回調函數通知咱們,這時候通知咱們就是執行foo中的回調,可是使用promise時,咱們要作的是偵聽來自foo的事件,而後在獲得通知的時候,根據狀況而定。

其中一個重要的好出就是,咱們能夠把這個事件中的偵聽對象提供給代碼中多個獨立的部分,在foo()完成的時候,他們均可以獨立的獲得通知:

var evt = foo();
    //讓bar()偵聽foo()的完成
    bar(evt);
    //讓baz()偵聽foo()的完成
    baz(evt);

上邊的例子中,bar和baz中不須要去知道或者關注foo中的實現細節。並且foo也不須要去關注baz和bar中的實現細節。

一樣的道理,在promise中,前面的代碼片斷會讓foo()建立並返回一個Promise實例,並且在這個Promise會被傳遞到bar()和baz()中。因此本質上,promise就是某個函數返回的對象。你能夠把回調函數綁定再這個對象上,而不是把回調函數當成參數傳進函數。

const promise = doSomething();
    promsie.then(successCallback,failureCallback){
        
    }

固然啦,promise不像舊式函數將回調函數傳遞到兩個處理函數中,並且會有一個優勢:

  • 在JavaScript事件隊列的本次tick運行完成以前,回調函數永遠不會執行。
  • 經過.then形式添加的回調函數,甚至都在異步操做完成以後才被添加的函數,都會被調用。
  • 經過屢次調用.then,能夠添加多個回調函數,他們會按照插入順序而且獨立運行。

可是,Promise最直接的好出就是鏈式調用。

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

而且在一個失敗操做以後還能夠繼續使用鏈式操做,即便鏈式中的一個動做失敗以後還能有助於新的動做繼續完成。

在調用Promise中的resolve()和reject()函數時若是帶有參數,那麼他們的參數會被傳遞給回調函數。

Promise.resolve()和Promise.reject()是手動建立一個已經resolve或者reject的promise快捷方法。一般,咱們可使用Promise.resolve()去鏈式調用一個由異步函數組成的數組。例如:

Promise.resolve().then(func1).then(func2);

Promise.all()和Promise。race()是並行運行異步操做的兩個組合式工具。

Promise.then()方法用來分別指定resolved狀態和rejected狀態的回調函數。傳遞到then中的函數被置入了一個微任務隊列,而不是當即執行,這意味着它是在JavaScript事件隊列的全部運行結束了,事件隊列被清空以後纔開始執行

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

Promise.then()方法返回一個Promise,它最多須要有兩個參數:Promise的成功和失敗狀況的回調函數。

p.then(onFulfilled, onRejected);

p.then(function(value) {
   // fulfillment
  }, function(reason) {
  // rejection
});

onFulfilled:當Promise變成接受狀態(fulfillment)時,該參數做爲回調函數被調用。該函數有一個參數,即接受的值。

onRejected:當Promise變成拒絕狀態時,該參數做爲回調函數被調用。該函數有一個參數,即拒絕的緣由。

Promise的狀態一旦改變,就永久保持該狀態,不會再改變了。

Promise中的錯誤處理

通常的狀況,咱們會在每次的Promise中拋出錯誤,在Promise中的then函數中的rejected處理函數會被調用,這是咱們做爲錯誤處理的經常使用方法:

let p = new Promise(function(resolve,reject){
        reject('error');
    });
    
    p.then(function(value){
        success(value);
    },function(error){
        error(error)   
    }
)

可是一種更好的方式是使用catch函數,這樣能夠處理Promise內部發生的錯誤,catch方法返回的仍是一個Promise對象,後邊還能夠接着調用then方法。並且catch方法儘可能寫在鏈式調用的最後一個,避免後邊的then方法的錯誤沒法捕獲。

let p = new Promise(function(resolve,reject){
        reject('error');
    });
    
    p.then(function(value){
        success(value);
    }).catch(function(error){
        console.log('error');
    }}

Promise.finally()函數,該方法是ES2018引入標準的。指定無論Promise對象最後狀態如何,都會執行的操做。finally方法的回調函數不接受任何參數,這意味着沒有辦法知道,前面的Promise狀態究竟是fulfilled仍是rejected。這標明,finally方法裏面的操做,是與狀態無關的,不依賴於Promise的執行結果。


上述文章,若有錯誤,還請指正,謝謝!!!

參考

相關文章
相關標籤/搜索