本文首發於個人博客,轉載請註明出處:http://kohpoll.github.io/blog/2016/05/02/the-promise-you-may-not-know/javascript
開始以前,咱們先來一個「腦筋急轉彎」。假設 later
函數調用後返回一個 promise 對象,下面這 4 種寫法有什麼區別?html
// #1 later(1000) .then(function() { return later(2000); }); // #2 later(1000) .then(function() { later(2000); }); // #3 later(1000) .then(later(2000)); // #4 later(1000) .then(later);
你們能夠先實際去運行代碼看看輸出結果:#1、#2、#3、#4,想一想爲何是這樣的輸出,而後回來繼續閱讀。java
Promise 是對異步處理的一種抽象。在 JavaScript 中,咱們一般使用回調函數來進行異步處理:git
later(1000, function(err) { if (err) { // 失敗時的處理 } else { // 成功時的處理 } });
Promise 將異步處理進行抽象,並造成規範化的接口:github
later(1000) .then(function() { // 成功時的處理 }) .catch(function(err) { // 失敗時的處理 });
基於固定、統一的接口編程,咱們能夠將複雜的異步處理模式化。編程
new Promise(function fn(resolve, reject) {})
會返回一個狀態爲 pending 的 promise 對象api
在 fn 中指定處理邏輯數組
處理成功時,調用 resolve(結果),promise 對象狀態變動爲 fulfilledpromise
處理失敗時,調用 reject(Error 對象),promise 對象狀態變動爲 rejectedbash
const later = function(timeout) { return new Promise(function fn(resolve, reject) { timeout = parseInt(timeout, 10); if (!timeout) { return reject(new Error('Invalid Timeout')); } return setTimeout(function() { resolve('later_' + timeout); }, timeout); }); };
獲取到 promise 對象後,咱們能夠爲其添加處理方法:
當對象被 resolve 時的處理方法(onFulfilled)
當對象被 reject 時的處理方法(onRejected)
later(1000) .then(function onFulfilled(data) {}) .catch(function onRejected(err) {}); later(1000) .then( function onFulfilled(data) {}, function onRejected(err) {} );
later(1000).then(function() { // 在這裏能作些什麼特別的? });
返回一個 promise 對象
返回一個同步值(什麼也不返回,那就是返回 undefined)
throw 一個 Error
對象
返回一個 promise 對象就是在作異步操做的串行化。
later(1000) .then(function(data) { // data = later_1000 return later(2000); }) .then(function(data) { // data = later_2000 });
上面的代碼會在 1 秒後得到第一個 promise 對象的結果,而後再過 2 秒後得到第二個 promise 對象的結果。
返回一個同步值能夠將同步代碼 「promise 化」。
later(1000) .then(function(data) { // data = later_1000 if (data != 'later_1000') { return later(1000); } return data; }) .then(function(data) { // data = later_1000 });
上面的代碼保證最後得到的結果老是 later_1000
(只是等 1 秒仍是 2 秒的區別)。更實用的例子是異步獲取某個數據(查詢 db),咱們能夠先從本地 cache 查詢,查到直接返回同步值,不然返回一個查詢 db 的 promise 對象,最終都會得到正確的數據。
函數什麼都不返回等於返回了 undefined
,因此當心下面的寫法:
later(1000) .then(function(data) { // data = later_1000 later(2000); }) .then(function(data) { // data = undefined });
上面的代碼在 1 秒後取到第一個 promise 對象的結果,而後不會等待第二個 promise 對象的結果,立刻就執行到了第二個 .then()
。
在 .then()
裏面拋出一個 Error
對象可讓錯誤處理更加方便。
later(1000) .then(function(data) { if (data == 'later_1000') { throw new Error('later_1000 invalid'); } if (data == 'later_2000') { return data; } return later(3000); }) .then(function(data) { }) .catch(function(err) { // 捕獲到錯誤 Error('later_1000 invalid'); });
只要咱們調用 catch
添加了 onRejected
回調處理,在 then()
裏面 throw 出的任何同步錯誤都會在 catch()
裏面被捕捉到(好比:不當心訪問了未定義值啊、JSON.parse 錯誤啊等等),這讓問題定位很是方便。
能捕獲到的是「同步」錯誤,請當心下面的代碼:
later(1000) .then(function(data) { setTimeout(function() { throw new Error('the err can not catch'); }, 1000); }) .then(function(data) { // data = undefined }) .catch(function(err) { // 捕獲不到錯誤 });
另外須要注意 .then(null, onRejected)
並不徹底等價於 .catch(onRejected)
:
later(1000) .then(function(data) { throw new Error('this is err'); }) .catch(function(err) { // 捕獲到錯誤 Error('this is err'); }); later(1000) .then( function(data) { throw new Error('this is err'); }, function(err) { // 捕獲不到錯誤 Error('this is err'); } );
咱們總結下這一部分的最佳實踐:
老是在 .then()
裏面使用 return
來返回 promise 對象或者同步值
老是在 .then()
裏面 throw
同步的 Error
對象
老是使用 .catch()
來捕獲錯誤
.then()
函數傳遞什麼目前爲止,咱們看到給 .then()
傳遞的都是函數,可是其實它能夠接受非函數值:
later(1000) .then(later(2000)) .then(function(data) { // data = later_1000 });
給 .then()
傳遞非函數值時,實際上會被解析成 .then(null)
,從而致使上一個 promise 對象的結果被「穿透」。因而,上面的代碼等價於:
later(1000) .then(null) .then(function(data) { // data = later_1000 });
爲了不沒必要要的麻煩,建議老是給 .then()
傳遞函數。
上面咱們主要經過 new Promise(fn)
的方式來建立 promise 對象,實際上有一個快捷方法 Promise.resolve(value)
能夠方便的建立 promise 對象。
Promise.resolve
的使用場景主要包括:
用最少的代碼快速建立一個 promise 對象
在 promise 化的 API 接口中將同步代碼 promise 化,更好的捕捉同步代碼產出的錯誤
下面兩種寫法是等價的,顯然使用 Promise.resolve
更加簡練:
new Promise(function(resolve, reject) { resolve('value'); }).then(function(data) {}); Promise.resolve('value').then(function(data) {});
在 promise 化的 API 中,將同步代碼 promise 化能夠統一的在 .catch()
中捕獲異常:
function apiReturnPromise() { return Promise.resolve().then(function() { someFuncMayThrowError(); return 'xyz'; }).then(function(data) { return doAsync(data); }); } apiReturnPromise() .then(function(data) {}) .catch(function(err) { // 能夠一致的捕捉到錯誤 });
實際編碼中咱們可能常常遇到一個 promise 對象依賴另外一個 promise 對象的執行,而且咱們兩個 promise 對象的結果都須要的狀況:
later(1000) .then(function(dataA) { return later(2000); }) .then(function(dataB) { // 咱們同時須要 dataA 和 dataB });
前面咱們說到在 .then()
裏能夠返回 promise 對象,這個 promise 對象其實是能夠調用本身的 .then()
的。下面的代碼能夠知足需求:
later(1000) .then(function(dataA) { return later(2000).then(function(dataB) { return dataA + ':' + dataB; }); }) .then(function(data) { // data = later_1000:later_2000 });
在異步處理中,常常須要結合 for 循環來進行批量處理。由於處理都是異步的,這個過程就相應的被分爲了兩類:
並行:一批異步操做同時執行
串行:一批異步操做挨個執行(一個操做完成後下一個操做才繼續)
promise 對象是對異步操做的一個抽象表示,對 promise 對象進行不一樣的操做能夠達到異步處理的並行和串行。
Promise.all
方法能夠接受一個數組(數組裏面的元素是 promise 對象)。當數組內全部 promise 對象變爲 fulfilled 狀態時,才調用 .then()
;數組內有任一個 promise 對象變爲 rejectec 狀態時,調用 .catch()
。
const promises = [1000, 2000, 3000].map(function(timeout) { return later(timeout); }); Promise.all(promises) .then(function(data) { // data = ['later_1000', 'later_2000', 'later_3000'] }) .catch(function(err) { });
數組內 promise 對象所表示的異步操做是同時執行的,而且最後的結果和傳遞給 Promise.all
的數組的順序是一致的。因此,3 秒鐘後咱們取得的結果是一個值爲 ['later_1000', 'later_2000', 'later_3000']
的數組。
在 .then()
裏面返回一個 promise 對象就是一種串行。咱們須要構造一個相似下面這樣的 promise 對象:
promise1 .then(function() { return promise2; }) .then(function() { return promise3; }) .then(...);
將一個數組轉換爲一個值,使用 reduce 能夠實現:
[1000, 2000, 3000] .reduce(function(promise, timeout) { return promise.then(function() { return later(timeout); }); }, Promise.resolve()) .then(function(data) { // data = later_3000 }) .catch(function(err) { });
上面代碼將如下面的方式來執行:
Promise.resolve() .then(function() { return later(1000); }) .then(function() { return later(2000); }) .then(function() { return later(3000) }) .then(function(data) { // data = later_3000 }) .catch(function(err) { });
每一個 promise 對象表示的異步操做依次執行,最終結果將會在 6 秒後取得(只能取到最後一個 promise 對象的結果,若是都須要的話,須要單獨進行處理存儲)。
later(1000) .then(function() { return later(2000); }) .then(done);
執行結果:
later(1000) later(2000) done(later_2000) |-----1s-----|----------2s----------|
later(1000) .then(function() { later(2000); }) .then(done);
執行結果:
later(1000) done(undefined) |-----1s-----| later(2000) |----------2s----------|
later(1000) .then(later(2000)) .then(done);
執行結果:
later(1000) done(later_1000) |-----1s-----| later(2000) |----------2s----------|
later(1000) .then(later) .then(done);
執行結果:
later(1000) later(later_1000) |-----1s-----| throw new Error('Invalid Timeout')