Promise
對象是用於異步操做的。javascript
Promise
的真正強大之處在於它的多重鏈式調用,能夠避免層層嵌套回調。若是咱們在第一次ajax請求後,還要用它返回的結果再次請求呢?html
使用Promise
,咱們就能夠利用then
進行「鏈式回調」,將異步操做以同步操做的流程表示出來。java
如下是個小Demo:ajax
/* e.g */ sendRequest('test1.html', '').then(function(data1) { console.log('第一次請求成功, 這是返回的數據:', data1); return sendRequest('test2.html', data1); }).then(function(data2) { console.log('第二次請求成功, 這是返回的數據:', data2); return sendRequest('test3.html', data2); }).then(function(data3) { console.log('第三次請求成功, 這是返回的數據:', data3); }).catch(function(error) { //用catch捕捉前面的錯誤 console.log('sorry, 請求失敗了, 這是失敗信息:', error); });
上一小節咱們認識了promise
長什麼樣,但對它用到的resolve
、reject
、then
、catch
想必還不理解。下面咱們一步步學習。promise
Promise
對象表明一個未完成、但預計未來會完成的操做。
它有如下三種狀態:異步
pending
:初始值,不是fulfilled,也不是rejectedfulfilled
:表明操做成功rejected
:表明操做失敗Promise
有兩種狀態改變的方式,既能夠從pending
轉變爲fulfilled
,也能夠從pending
轉變爲rejected
。一旦狀態改變,就「凝固」了,會一直保持這個狀態,不會再發生變化。當狀態發生變化,promise.then
綁定的函數就會被調用。
注意:Promise
一旦新建就會「當即執行」,沒法取消。這也是它的缺點之一。
下面就經過例子進一步講解。async
/* 例3.1 */ //構建Promise var promise = new Promise(function (resolve, reject) { if (/* 異步操做成功 */) { resolve(data); } else { /* 異步操做失敗 */ reject(error); } });
相似構建對象,咱們使用new
來構建一個Promise
。Promise
接受一個「函數」做爲參數,該函數的兩個參數分別是resolve
和reject
。這兩個函數就是就是「回調函數」,由JavaScript引擎提供。ide
resolve
函數的做用:在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去; reject
函數的做用:在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。函數
Promise實例生成之後,能夠用then
方法指定resolved
狀態和reject
狀態的回調函數。學習
/* 接例3.1 */ promise.then(onFulfilled, onRejected); promise.then(function(data) { // do something when success }, function(error) { // do something when failure });
then
方法會返回一個Promise。它有兩個參數,分別爲Promise從pending
變爲fulfilled
和rejected
時的回調函數(第二個參數非必選)。這兩個函數都接受Promise對象傳出的值做爲參數。
簡單來講,then
就是定義resolve
和reject
函數的,其resolve
參數至關於:
function resolveFun(data) { //data爲promise傳出的值 }
而新建Promise中的'resolve(data)',則至關於執行resolveFun函數。
Promise新建後就會當即執行。而then
方法中指定的回調函數,將在當前腳本全部同步任務執行完纔會執行。以下例:
/* 例3.2 */ var promise = new Promise(function(resolve, reject) { console.log('before resolved'); resolve(); console.log('after resolved'); }); promise.then(function() { console.log('resolved'); }); console.log('outer'); -------output------- before resolved after resolved outer resolved
因爲resolve
指定的是異步操做成功後的回調函數,它須要等全部同步代碼執行後纔會執行,所以最後打印'resolved',這個和例2.2是同樣的道理。
通過上一章的學習,相信你們已經學會使用Promise
。
總結一下建立promise的流程:
new Promise(fn)
或者它的快捷方式Promise.resolve()
、Promise.reject()
,返回一個promise對象fn
中指定異步的處理resolve
reject
若是使用ES6的箭頭函數,將會使寫法更加簡單清晰。
這一章節,將會用例子的形式,以說明promise使用過程當中的注意點及容易犯的錯誤。
情景1:reject 和 catch 的區別
onFulfilled
中發生異常的話,在onRejected
中是捕獲不到這個異常的。.then
中產生的異常能在.catch
中捕獲通常狀況,仍是建議使用第二種,由於能捕獲以前的全部異常。固然了,第二種的.catch()
也能夠使用.then()
表示,它們本質上是沒有區別的,.catch === .then(null, onRejected)
情景2:若是在then中拋錯,而沒有對錯誤進行處理(即catch),那麼會一直保持reject狀態,直到catch了錯誤
/* 例4.1 */ function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); -------output------- Catch Error: A or B,ReferenceError: x is not defined Final Task
根據例4.1的輸出結果及流程圖,能夠看出,A拋錯時,會按照 taskA → onRejected → finalTask這個流程來處理。A拋錯後,若沒有對它進行處理,如例3.7,狀態就會維持rejected
,taskB不會執行,直到catch
了錯誤。
/* 例4.2 */ function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejectedA(error) { console.log("Catch Error: A", error); } function onRejectedB(error) { console.log("Catch Error: B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .catch(onRejectedA) .then(taskB) .catch(onRejectedB) .then(finalTask); -------output------- Catch Error: A ReferenceError: x is not defined Task B Final Task
將例4.2與4.1對比,在taskA後多了對A的處理,所以,A拋錯時,會按照A會按照 taskA → onRejectedA → taskB → finalTask這個流程來處理,此時taskB是正常執行的。
情景3:每次調用then
都會返回一個新建立的promise對象,而then
內部只是返回的數據
/* 例4.3 */ //方法1:對同一個promise對象同時調用 then 方法 var p1 = new Promise(function (resolve) { resolve(100); }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { console.log("finally: " + value); }); -------output------- finally: 100 //方法2:對 then 進行 promise chain 方式進行調用 var p2 = new Promise(function (resolve) { resolve(100); }); p2.then(function (value) { return value * 2; }).then(function (value) { return value * 2; }).then(function (value) { console.log("finally: " + value); }); -------output------- finally: 400
第一種方法中,then
的調用幾乎是同時開始執行的,且傳給每一個then的value都是100,這種方法應當避免。方法二纔是正確的鏈式調用。
所以容易出現下面的錯誤寫法:
/* 例4.4 */ function badAsyncCall(data) { var promise = Promise.resolve(data); promise.then(function(value) { //do something return value + 1; }); return promise; } badAsyncCall(10).then(function(value) { console.log(value); //想要獲得11,實際輸出10 }); -------output------- 10
正確的寫法應該是:
/* 改寫例4.4 */ function goodAsyncCall(data) { var promise = Promise.resolve(data); return promise.then(function(value) { //do something return value + 1; }); } goodAsyncCall(10).then(function(value) { console.log(value); }); -------output------- 11
情景4:在異步回調中拋錯,不會被catch
到
// Errors thrown inside asynchronous functions will act like uncaught errors var promise = new Promise(function(resolve, reject) { setTimeout(function() { throw 'Uncaught Exception!'; }, 1000); }); promise.catch(function(e) { console.log(e); //This is never called });
情景5: promise狀態變爲resove
或reject
,就凝固了,不會再改變
console.log(1); new Promise(function (resolve, reject){ reject(); setTimeout(function (){ resolve(); //not called }, 0); }).then(function(){ console.log(2); }, function(){ console.log(3); }); console.log(4); -------output------- 1 4 3