該新特性屬於 ECMAScript 2015(ES6)規範,在使用時請注意瀏覽器兼容性。node
因爲ES6原生提供Promise
,因此無需安裝Promise
庫。但在ES5環境下咱們可使用bluebird
庫來提供Promise。
web
背景知識:數據庫
理解同樣東西,固然要先了解它是怎麼來的json
JavaScript是單線程的,這意味着代碼是按順序執行的。對於瀏覽器而言,JavaScript代碼和其餘任務共享一個線程,不一樣的瀏覽器略有差別,但大致上這些和JavaScript共享線程的任務主要包括重繪、更新樣式、用戶交互等,全部這些任務操做都會阻塞其餘任務。segmentfault
避免事件阻塞的經常使用方法是使用事件監聽器。咱們能夠爲某些特定事件設置監聽器,若是事件發生的話,便馬上觸發監聽器,你應該已經習慣使用回調函數來解決這個問題了,例如:數組
var img1 = document.querySelector('.img-1'); img1.addEventListener('load', function() { // 圖片加載完成 }); img1.addEventListener('error', function() { // 出問題了 });
上面的代碼中,咱們添加了兩個監聽器,請求圖片,回調函數只在事件發生的時候纔會被觸發。可是經過事件機制還存在幾個問題:promise
事件在綁定以前就發生了怎麼辦?瀏覽器
在添加監聽器以前,圖片加載發生了錯誤怎麼辦?網絡
僅僅是一張圖片就存在這麼多問題,那麼若是有一堆圖片要處理,又該怎麼辦?一個愈來愈流行的異步解決方案Promise來拯救。異步
再如:
異步方法調用中,每每會出現回調函數一環扣一環的狀況。這種狀況致使了回調金字塔問題的出現。不只代碼寫起來費勁又不美觀,並且問題複雜的時候,閱讀代碼的人也難以理解。
舉例以下:
db.save(data, function(data){ // do something... db.save(data1, function(data){ // do something... db.save(data2, function(data){ // do something... done(data3); // 返回數據 }) }); });
假設有一個數據庫保存操做,一次請求須要在三個表中保存三次數據。那麼咱們的代碼就跟上面的代碼類似了。這時候假設在第二個db.save出了問題怎麼辦?基於這個考慮,咱們又須要在每一層回調中使用相似try...catch這樣的邏輯。這個就是萬惡的來源,也是node剛開始廣爲詬病的一點。
另一個缺點就是,假設咱們的三次保存之間並無先後依賴關係,咱們仍然須要等待前面的函數執行完畢, 才能執行下一步,而沒法三個保存並行,以後返回一個三個保存事後須要的結果。(或者說實現起來須要技巧)
Promise
JavaScript的一大特色就是會涉及到大量的異步代碼。同步代碼一般易於理解和調試,而異步代碼則具備更好的性能和靈活性。
目前Promise正逐漸稱爲JavaScript世界的一個重要組成部分,而且不少新的API也都基於Promise進行了實現。
目前已經有一些原生API使用了Promise,包括:
· Battery API
· Fetch API
· ServiceWorker API
所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。是ES6規範新增的對象,它能夠用於延遲計算和異步計算。
一個Promise對象表明着一個還未完成,但預期會完成的操做。須要記住:
· 一個Promise要麼成功要麼失敗,而且狀態不可變
· 能夠根據Promise的結果設置特定的回調函數
語法:
new Promise(executor); new Promise(function(resolve, reject) { ... });
new Promise()構造器應該只被用於傳統的異步任務上,例如setTimeout或XMLHttpRequest。
經過new關鍵字建立一個新的Promise,它接收一個回調函數做爲參數,這個函數在建立Promise對象的時候會當即獲得執行,該回調函數又包括了兩個特定的回調函數,分別被命名爲resolve和reject,成功後調用resolve,失敗則調用reject。這個函數一般被用來執行一些異步操做,操做完成之後能夠選擇調用成功回調函數(resolve)來觸發promise的成功狀態,或者,在出現錯誤的時候調用失敗回調函數(reject)來觸發promise的失敗。
根據不一樣的任務,由開發者來決定resolve和reject在函數體內的位置。
描述
Promise 對象是一個返回值的代理,這個返回值在promise對象建立時未必已知。它容許你爲異步操做的成功返回值或失敗信息指定處理方法。 這使得異步方法能夠像同步方法那樣返回值:異步方法會返回一個包含了原返回值的 promise 對象來替代原返回值。
Promise對象有如下幾種狀態:
pending: 初始狀態, 既不是 fulfilled 也不是 rejected.
fulfilled: 成功的操做.
rejected: 失敗的操做.
狀態轉換關係爲:pending->fulfilled,pending->rejected。
隨着狀態的轉換將觸發各類事件(如執行成功事件、執行失敗事件等)。
pending狀態的promise對象既可轉換爲帶着一個成功值的fulfilled 狀態,也可變爲帶着一個失敗信息的 rejected 狀態。當狀態發生轉換時,promise.then綁定的方法(函數句柄)就會被調用。(當綁定方法時,若是 promise對象已經處於 fulfilled 或 rejected 狀態,那麼相應的方法將會被馬上調用, 因此在異步操做的完成狀況和它的綁定方法之間不存在競爭條件。)
注意:若是一個promise對象處在fulfilled或rejected狀態而不是pending狀態,那麼它也能夠被稱爲settled狀態。你可能也會聽到一個術語resolved ,它表示promise對象處於settled狀態,或者promise對象被鎖定在了調用鏈中。關於promise的狀態, Domenic Denicola 的 States and fates 有更多詳情可供參考。
由於Promise.prototype.then和 Promise.prototype.catch方法返回 promises對象, 因此它們能夠被鏈式調用—— 一種被稱爲 composition 的操做。
示例:
var promise = new Promise(function func(resolve, reject){ // do somthing, maybe async if (success){ return resolve(data); } else { return reject(data); } }); promise.then(function(data){ // do something... e.g console.log(data); }, function(err){ // deal the err. })
promise對象在建立的時候會執行func函數中的邏輯。
邏輯處理完畢而且沒有錯誤時,resolve這個回調會將值傳遞到一個特殊的地方。這個特殊的地方在哪呢?就是下面代碼中的then,咱們使用then中的回調函數來處理resolve後的結果。好比上面的代碼中,咱們將值簡單的輸出到控制檯。若是有錯誤,則reject到then的第二個回調函數中,對錯誤進行處理。
使用Promise則很是的簡單,能夠調用Promise對象的then()方法來處理異步計算的結果。then接收兩個回調函數,分別是成功的回調函數和失敗時的回調函數,這兩個參數都是可選的。
Promise的使用有兩點須要記住的:
· then()方法能夠鏈式調用
· catch()方法能夠做爲錯誤處理語句的語法糖,至關於then(undefined, function(error) { ... });
再來看一個例子:用於將XMLHttpRequest轉換爲一個基於Promise的接口。
咱們以GET請求爲例:
function get(url) { // 返回一個新的 Promise return new Promise(function(resolve, reject) { // 經典 XHR 操做 var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function() { // 當發生 404 等情況的時候調用此函數 // 因此先檢查狀態碼 if (req.status == 200) { // 以響應文本爲結果,完成此 Promise resolve(req.response); } else { // 不然就以狀態碼爲結果否認掉此 Promise // (提供一個有意義的 Error 對象) reject(Error(req.statusText)); } }; // 網絡異常的處理方法 req.onerror = function() { reject(Error("Network Error")); }; // 發出請求 req.send(); }); }
咱們如今能夠這麼調用它:
get('story.json').then(function(response) { console.log("Success!", response); }, function(error) { console.error("Failed!", error); });
如今咱們發起XHR請求便變得簡單直觀的多了。story.json文件的內容以下:
{ "heading": "A story about something", "chapterUrls": [ "chapter-1.json", "chapter-2.json", "chapter-3.json", "chapter-4.json", "chapter-5.json" ] }
鏈式調用
上面咱們說過then()接收兩個參數,分別對應成功和失敗時的回調函數。咱們還能夠將多個then方法串聯起來,用於修改結果或執行更多的異步操做。
promise的then方法依然可以返回一個Promise對象,這樣咱們就又能用下一個then來作同樣的處理。
第一個then中的兩個回調函數決定第一個then返回的是一個什麼樣的Promise對象:
· 假設第一個then的第一個回調沒有返回一個Promise對象,那麼第二個then的調用者仍是原來的Promise對象,只不過其resolve的值變成了第一個then中第一個回調函數的返回值。
· 假設第一個then的第一個回調函數返回了一個Promise對象,那麼第二個then的調用者變成了這個新的Promise對象,第二個then等待這個新的Promise對象resolve或者reject以後執行回調。
你能夠對結果進行修改,而後返回一個新的值,例如:
new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { resolve(10); }, 3000); }) .then(num => { console.log('first then: ', num); return num * 2; }) .then(num => { console.log('second then: ', num); return num * 2; }) .then(num => { console.log('last then: ', num);}); // From the console: // first then: 10 // second then: 20 // last then: 40
每一個then接收前一個then的返回值的結果。
回到以前的get函數,咱們能夠修改返回值的類型,將結果進行必定的轉換:
get('story.json').then(function(response) { return JSON.parse(response); }).then(function(response) { console.log("Yey JSON!", response); });
爲了讓代碼變得更簡單,能夠再次進行改進:
由於JSON.parse只接收一個參數,並返回轉換後的結果,咱們能夠直接使用then(JSON.parse)
then中的回調函數,咱們能夠直接使用ES6的胖箭頭函數,這樣可讓代碼更直觀
get('story.json').then(JSON.parse).then(response => console.log("JSON data: ", response);
因爲這段代碼會被重複調用,咱們能夠定義一個新的getJSON函數:
function getJSON(url) { return get(url).then(JSON.parse); // 返回一個獲取JSON並加以解析的Promise }
對於串聯起來的then()方法而言:若是你返回了一個值,那麼它就會被傳給下一個then()的回調。
若是你返回一個「類Promise」對象,則下一個then()就會等待這個Promise明確結束(成功/失敗)纔會執行。
getJSON('story.json') .then(story => getJSON(story.chapterUrls[0])) .then(chapter => console.log("Got chapter 1!, " chapter));
在上面的代碼中,咱們首先發起對story.json的異步請求,它會返回給咱們一個URL列表,而後咱們請求其中的第一個。
錯誤處理
前面咱們已經知道,then接收兩個參數,一個處理成功時的回調函數,一個處理失敗時的回調函數。
get('story.json').then(function(response) { console.log("Success!", response); }, function(error) { console.log("Failed!", error); });
你還可使用catch來進行錯誤處理,實際上,它不過是then(undefined, func)的語法糖而已。這樣可以讓代碼更直觀:
get('story.json') .then(response => console.log('Success!', response)) .catch(error => console.error('Failed!', error));
promise還有它的一些屬性和方法:
屬性
Promise.length:長度屬性,其值爲 1 (構造器參數的數目).
Promise.prototype:表示 Promise 構造器的原型.
方法
Promise.all(iterable)
這個方法返回一個新的promise對象,該promise對象在iterable裏全部的promise對象都成功的時候纔會觸發成功,一旦有任何一個iterable裏面的promise對象失敗則當即觸發該promise對象的失敗。這個新的promise對象在觸發成功狀態之後,會把一個包含iterable裏全部promise返回值的數組做爲成功回調的返回值,順序跟iterable的順序保持一致;若是這個新的promise對象觸發了失敗狀態,它會把iterable裏第一個觸發失敗的promise對象的錯誤信息做爲它的失敗錯誤信息。Promise.all方法常被用於處理多個promise對象的狀態集合。(能夠參考jQuery.when方法---譯者注)
Promise.race(iterable)
當iterable參數裏的任意一個子promise被成功或失敗後,父promise立刻也會用子promise的成功返回值或失敗詳情做爲參數調用父promise綁定的相應句柄,並返回該promise對象。
Promise.reject(reason)
調用Promise的rejected句柄,並返回這個Promise對象。
Promise.resolve(value)
用成功值value完成一個Promise對象。若是該value爲可繼續的(thenable,即帶有then方法),返回的Promise對象會「跟隨」這個value,採用這個value的最終狀態;不然的話返回值會用這個value知足(fullfil)返回的Promise對象。
參考: