從JavaScript的事件迴圈到Promise

來源:互聯網
上載者:User

從JavaScript的事件迴圈到Promise

JS線程是單線程運行機制,就是自己按順序做自己的事,瀏覽器線程用於互動和控制,JS可以操作DOM元素,說起JS中的非同步時,我們需要注意的是,JS中其實有兩種非同步,一種是基於瀏覽器的非同步IO,比如Ajax,另外一種是基於計時方法setTimeout和setInterval的非同步。

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

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

對於setTimeout和setInterval來說,當js線程執行到該程式碼片段時,js主線程會根據所設定的時間,當設定的時間到期時,將設定的回呼函數放到事件隊列中,然後js主線程繼續去執行下邊的代碼,當js線程執行完主線程上的代碼之後,會去迴圈執行事件隊列中的函數。至於setTimeout或者setInterval設定的執行時間在實際表現時會有一些偏差,普遍的一個解釋為,當定時任務的時間到期時,本應該去執行該回呼函數,但是這時js主線程可能還有任務在執行,或者是該回呼函數再事件隊列中排的比較靠後,就導致該回呼函數執行時間與定時器設定時間不一致。

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

eventloop是一個用作隊列的數組,eventloop是一個一直在迴圈執行的,迴圈的每一輪成為一個tick,在每一個tick中,如果隊列中有等待事件,那麼就會從隊列中摘取下一個事件進行執行,這些事件就是我們之前的回呼函數。現在ES6精確指定了事件迴圈的工作細節,這意味著在技術上將其納入了JavaScript引擎的勢力範圍,而不只是由宿主環境決定了,主要的一個原因是ES6中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的執行結果。

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

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.