JavaScript運行在瀏覽器中,是以單線程的方式運行的;JavaScript爲何不選擇提升整個應用性能和吞吐量實現應用並行的多線程呢?javascript
這在JavaScript的產生就決定了:當初JavaScript是用來處理用戶和頁面的交互,以及操做DOM樹,CSS樣式樹來給用戶呈現一份動態而豐富的交互體驗和服務器邏輯的交互處理。若是是多線程的來操做DOM樹,則會發生預想不到的衝突,一個線程A想要刪除DOM節點,另一個線程B想要修改這個DOM節點,這樣就會致使瀏覽器不知道聽取哪個操做。因此JavaScript從誕生就決定了是單線程方式運行的。java
由於單線程的執行,因此在同一時間內只能執行一個特定的任務,而且會阻塞其餘任務。因此對於耗時很長的任務來講,例如對於I/O設備的訪問,其實並無必要等待它的完成,徹底能夠在執行這項任務以前,JavaScript去執行另一個任務,直到I/O任務執行完成後再繼續執行該任務的處理就好了。因此JavaScript對於這種耗時操做都會被處理爲異步操做,以及回調註冊機制,等到這些任務完成後就將後續的處理操做封裝爲JavaScript任務放入執行任務隊列中,等待JavaScript線程空閒時去執行,所以這裏就有了「瀏覽器事件循環」的機制promise
JavaScript不少任務都是異步的,包括鍵盤、鼠標I/O輸入輸出事件、Ajax請求網絡I/O回調等。當這些異步的任務發生時,它們都會被放入瀏覽器事件任務隊列中。在瀏覽器中有一個叫作消息循環池(Event Loop),JavaScript引擎在運行時候單線程的處理這些任務,它們會被放入在這個事件循環池中,須要等到JavaScript運行時執行線程空閒時候纔會按照隊列先進先出的原則被一一執行。可是因爲此時JavaScript主線程也許並不空閒,因此它們不會被當即執行。瀏覽器
雖然JavaScript是單線程執行的,可是瀏覽器是多線程執行的,瀏覽器有JavaScript的執行線程、UI節點渲染線程,圖片加載線程以及Ajax請求線程等等,在Chrome設計中存在不少的進程,並利用進程間通信來完成它們之間的同步,所以這也是Chrome快速的法寶之一。對於Ajax的請求也須要特殊線程來執行,當須要發送一個Ajax請求的時候,瀏覽器會開闢一個新的線程來執行HTTP的請求,它並不會阻塞JavaScript線程的執行,HTTP請求狀態變動事件會被做爲回調放入到瀏覽器的事件隊列中等待被執行。服務器
3、異步出現的問題網絡
由上咱們能夠知道,JavaScript的不少任務都是異步處理的,以callback回調的方式處理事件任務,可是對於多個JavaScript異步任務的處理,將會碰到以下狀況:多線程
1 function1(data, function (data1){ 2 3 function2(data1, function (data2){ 4 5 function3(data2, function (data3){ 6 // .... 一層套一層的回調 7 }); 8 9 }); 10 11 });
咱們把這種每一層的回調函數都須要依賴上一層的回調執行完,因此造成了層層嵌套的關係,這樣的現象叫作「回調地獄」,這樣的代碼很是不易於閱讀與維護,爲了解決這個問題,」Promise「出現了!異步
4、Promise的出現函數
1)Promise被翻譯爲」承諾「,它表示若是A調用了一個長時間的B任務的時候,B將會返回一個」承諾「給A,A就不用關心整個實施的過程,繼續作本身的任務;當B實施完成的時候,會經過A,並將執行A之間的預先約定好的回調函數;Promise解決的問題是一種帶有延遲的事件,這個事件會被延遲到將來某個合適的時間點在執行。oop
2)Promise有三種狀態,分別是:Pending——初始狀態,等到任務的完成或者拒絕;Fullfilled——任務執行完成而且成功狀態;Rejected——任務執行完成而且失敗的狀態
3)Promise對象必須實現then方法,then是Promise規範的核心,並且then方法也必須返回一個Promise對象,同一個Promise對象能夠註冊多個then方法,而且回調的執行順序跟他們註冊的順序一致
4)then方法接受兩個回調函數:分別是成功時的回調和失敗時候的回調;而且它們分別在:Promise由「Pending」狀態轉換到「Fulfilled」狀態時被調用和在Promise由「Pending」狀態轉換到「Rejected」狀態時被調用。
因此上述代碼,使用Promise能夠轉換爲:
function1(data) .then(function(data1){ return function2(data1); }) .then(function(data2){ return function3(data2); }) // 仍然能夠繼續then方法
Promise將原來回調地獄中的回調函數,從橫向式增長變味了縱向增加。以鏈式的風格,使得代碼更加可讀和維護
5、Promise的使用
1)多個異步任務的串行處理
使用Angular中$http的實現:
$http.get('/demo1') .then(function(data){ console.log('demo1', data); return $http.get('/demo2', {params: data.result}); }) .then(function(data){ console.log('demo2', data); return $http.get('/demo3', {params: data.result}); }) .then(function(data){ console.log('demo3', data.result); });
then方法能夠一直延續下去,也能夠在縱向擴展的途中改變爲其餘Promise的數據;
2)多個異步任務的並行處理
不少場景下,咱們須要處理的多個異步任務並無那麼強的依賴關係,只須要在這一系列的異步任務所有完成的時候執行一些特定的邏輯。這個時候爲了性能的考慮,咱們不須要將他們串行執行,選擇並行是一個更好的選擇。若是仍採用回調函數,則用Promise就能夠解決
$q.all([$http.get('/demo1'), $http.get('/demo2'), $http.get('/demo3') ]) .then(function(results){ console.log('result 1', results[0]); console.log('result 2', results[1]); console.log('result 3', results[2]); });
這樣就能夠等到一堆異步任務完成後,在執行特定的業務回調了。
PS:在Angular中的路由機制ngRoute
、uiRoute
的resolve機制也是採用一樣的原理:在路由執行的時候,會將獲取模板的Promise、獲取全部resolve數據的Promise都拼接在一塊兒,同時並行的獲取它們,而後等待它們都結束的時候,纔開始初始化ng-view
、ui-view
指令的scope對象,以及compile模板節點,並插入頁面DOM中,完成一次路由的跳轉而且切換了View,將靜態的HTML模板變爲動態的網頁展現出來。
3)對於同步數據的Promise處理,統一調用接口
4)對於延遲任務的Promise DSL語義化封裝
5)利用Promise來實現管道式AOP攔截
6、參考資料
1.http://greengerong.com/blog/2015/10/27/javascript-single-thread-and-browser-event-loop/
2.http://greengerong.com/blog/2015/10/22/promisede-miao-yong/