Promise 多重鏈式調用

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長什麼樣,但對它用到的resolverejectthencatch想必還不理解。下面咱們一步步學習。promise

Promise對象表明一個未完成、但預計未來會完成的操做。
它有如下三種狀態:異步

  • pending:初始值,不是fulfilled,也不是rejected
  • fulfilled:表明操做成功
  • 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來構建一個PromisePromise接受一個「函數」做爲參數,該函數的兩個參數分別是resolvereject。這兩個函數就是就是「回調函數」,由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變爲fulfilledrejected時的回調函數(第二個參數非必選)。這兩個函數都接受Promise對象傳出的值做爲參數
簡單來講,then就是定義resolvereject函數的,其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
總結一下建立promise的流程:

  1. 使用new Promise(fn)或者它的快捷方式Promise.resolve()Promise.reject(),返回一個promise對象
  2. fn中指定異步的處理
    處理結果正常,調用resolve
    處理結果錯誤,調用reject

若是使用ES6的箭頭函數,將會使寫法更加簡單清晰。

這一章節,將會用例子的形式,以說明promise使用過程當中的注意點及容易犯的錯誤。

情景1:reject 和 catch 的區別

  • promise.then(onFulfilled, onRejected)
    onFulfilled中發生異常的話,在onRejected中是捕獲不到這個異常的。
  • promise.then(onFulfilled).catch(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

clipboard.png

根據例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狀態變爲resovereject,就凝固了,不會再改變

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

五 結語

相關文章
相關標籤/搜索