如果你使用過其他的Promise實現類庫的話,可能見過用`done`代替`then`的例子。
這些類庫都提供了?`Promise.prototype.done`?方法,使用起來也和?`then`?一樣,但是這個方法并不會返回promise對象。
雖然?[ES6 Promises](http://liubin.github.io/promises-book/#es6-promises)和[Promises/A+](http://liubin.github.io/promises-book/#promises-aplus)等在設計上并沒有對`Promise.prototype.done`?做出任何規定,但是很多實現類庫都提供了該方法的實現。
在本小節中,我們將會學習什么是?`Promise.prototype.done`?,以及為什么很多類庫都提供了對該方法的支持。
## 4.6.1\. 使用done的代碼示例
看一下實際使用done的代碼的例子的話,應該就非常容易理解?`done`?方法的行為了。
promise-done-example.js
~~~
if (typeof Promise.prototype.done === 'undefined') {
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected).catch(function (error) {
setTimeout(function () {
throw error;
}, 0);
});
};
}
var promise = Promise.resolve();
promise.done(function () {
JSON.parse('this is not json'); // => SyntaxError: JSON.parse
});
// => 請打開瀏覽器的開發者工具中的控制臺窗口看一下
~~~
在前面我們已經說過,promise設計規格并沒有對?`Promise.prototype.done`做出任何規定,因此在使用的時候,你可以使用已有類庫提供的實現,也可以自己去實現。
我們會在后面講述如何去自己實現,首先我們這里先對使用?`then`?和使用?`done`這兩種方式進行一下比較。
使用then的場景
~~~
var promise = Promise.resolve();
promise.then(function () {
JSON.parse("this is not json");
}).catch(function (error) {
console.error(error);// => "SyntaxError: JSON.parse"
});
~~~
從上面我們可以看出,兩者之間有以下不同點。
* `done`?并不返回promise對象
* 也就是說,在done之后不能使用?`catch`?等方法組成方法鏈
* `done`?中發生的異常會被直接拋給外面
* 也就是說,不會進行Promise的錯誤處理(Error Handling)
由于`done`?不會返回promise對象,所以我們不難理解它只能出現在一個方法鏈的最后。
此外,我們已經介紹過了Promise具有強大的錯誤處理機制,而`done`則會在函數中跳過錯誤處理,直接拋出異常。
為什么很多類庫都提供了這個和Promise功能相矛盾的函數呢?看一下下面Promise處理失敗的例子,也許我們多少就能理解其中原因了吧。
## 4.6.2\. 消失的錯誤
Promise雖然具備了強大的錯誤處理機制,但是(調試工具不能順利運行的時候)這個功能會導致人為錯誤(human error)更加復雜,這也是它的一個缺點。
也許你還記得,我們在?[then or catch?](http://liubin.github.io/promises-book/#then-or-catch)?中也看到了類似的內容。
像下面那樣,我們看一個能返回promise對象的函數。
json-promise.js
~~~
function JSONPromise(value) {
return new Promise(function (resolve) {
resolve(JSON.parse(value));
});
}
~~~
這個函數將接收到的參數傳遞給?`JSON.parse`?,并返回一個基于`JSON.parse`的promise對象。
我們可以像下面那樣使用這個Promise函數,由于?`JSON.parse`?會解析失敗并拋出一個異常,該異常會被?`catch`?捕獲。
~~~
function JSONPromise(value) {
return new Promise(function (resolve) {
resolve(JSON.parse(value));
});
}
// 運行示例
var string = "非合法json編碼字符串";
JSONPromise(string).then(function (object) {
console.log(object);
}).catch(function(error){
// => JSON.parse拋出異常時
console.error(error);
});
~~~
如果這個解析失敗的異常被正常捕獲的話則沒什么問題,但是如果編碼時忘記了處理該異常,一旦出現異常,那么查找異常發生的源頭將會變得非常棘手,這就是使用promise需要注意的一面。
忘記了使用catch進行異常處理的的例子
~~~
var string = "非合法json編碼字符串";
JSONPromise(string).then(function (object) {
console.log(object);
}); // 雖然拋出了異常,但是沒有對該異常進行處理
~~~
如果是`JSON.parse`?這樣比較好找的例子還算好說,如果是拼寫錯誤的話,那么發生了Syntax Error錯誤的話將會非常麻煩。
typo錯誤
~~~
var string = "{}";
JSONPromise(string).then(function (object) {
conosle.log(object);//存在conosle這個拼寫錯誤
});
~~~
這這個例子里,我們錯把?`console`?拼成了?`conosle`?,因此會發生如下錯誤。
>
>
> ReferenceError: conosle is not defined
>
>
但是,由于Promise的try-catch機制,這個問題可能會被內部消化掉。 如果在調用的時候每次都無遺漏的進行?`catch`?處理的話當然最好了,但是如果在實現的過程中出現了這個例子中的錯誤的話,那么進行錯誤排除的工作也會變得困難。
這種錯誤被內部消化的問題也被稱為?_unhandled rejection_?,從字面上看就是在Rejected時沒有找到相應處理的意思。
這種unhandled rejection錯誤到底有多難檢查,也依賴于Promise的實現。 比如?[ypromise](https://github.com/yahoo/ypromise)?在檢測到 unhandled rejection 錯誤的時候,會在控制臺上提示相應的信息。
>
>
> Promise rejected but no error handlers were registered to it
>
另外,?[Bluebird](https://github.com/petkaantonov/bluebird)?在比較明顯的人為錯誤,即ReferenceError等錯誤的時候,會直接顯示到控制臺上。
> "Possibly unhandled ReferenceError. conosle is not defined
>
原生(Native)的 Promise實現為了應對同樣問題,提供了GC-based unhandled rejection tracking功能。
該功能是在promise對象被垃圾回收器回收的時候,如果是unhandled rejection的話,則進行錯誤顯示的一種機制。
[Firefox](https://twitter.com/domenic/status/461154989856264192)?或?[Chrome](https://code.google.com/p/v8/issues/detail?id=3093)?的原生Promise都進行了部分實現。
## 4.6.3\. done的實現
作為方法論,在Promise中?`done`?是怎么解決上面提到的錯誤被忽略呢? 其實它的方法很簡單直接,那就是必須要進行錯誤處理。
由于可以在 Promise上實現?`done`?方法,因此我們看看如何對?`Promise.prototype.done`?這個Promise的prototype進行擴展。
promise-prototype-done.js
~~~
"use strict";
if (typeof Promise.prototype.done === "undefined") {
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected).catch(function (error) {
setTimeout(function () {
throw error;
}, 0);
});
};
}
~~~
那么它是如何將異常拋到Promise的外面的呢?其實這里我們利用的是在setTimeout中使用throw方法,直接將異常拋給了外部。
setTimeout的回調函數中拋出異常
~~~
try{
setTimeout(function callback() {
throw new Error("error");//這個例外不會被捕獲
}, 0);
}catch(error){
console.error(error);
}
~~~
> 關于為什么異步的`callback`中拋出的異常不會被捕獲的原因,可以參考下面內容。
> * [JavaScript和異步錯誤處理 - Yahoo! JAPAN Tech Blog(日語博客)](http://techblog.yahoo.co.jp/programming/javascript_error/)
仔細看一下?[`Promise.prototype.done`](http://liubin.github.io/promises-book/#promise-prototype-done.js)的代碼,我們會發現這個函數什么也沒?`return`?。 也就是說,?`done`按照「Promise chain在這里將會中斷,如果出現了異常,直接拋到promise外面即可」的原則進行了處理。
如果實現和運行環境實現的比較完美的話,就可以進行?_unhandled rejection_?檢測,`done`也不一定是必須的了。 另外像本小節中的?[`Promise.prototype.done`](http://liubin.github.io/promises-book/#promise-prototype-done.js)一樣,`done`也可以在既有的Promise之上進行實現,也可以說它沒有進入到?[ES6 Promises](http://liubin.github.io/promises-book/#es6-promises)的設計規范之中。
> 本文中的?`Promise.prototype.done`?的實現方法參考了?[promisejs.org](https://www.promisejs.org/)?。
## 4.6.4\. 總結
在本小節中,我們學習了?[Q](https://github.com/kriskowal/q/wiki/API-Reference#promisedoneonfulfilled-onrejected-onprogress)?、?[Bluebird](https://github.com/petkaantonov/bluebird)?和?[prfun](https://github.com/cscott/prfun#promisedone—undefined)?等Promise類庫提供的?`done`?的基礎和實現細節,以及`done`方法和?`then`?方法有什么區別等內容。
我們也學到了?`done`?有以下兩個特點。
* `done`?中出現的錯誤會被作為異常拋出
* 終結 Promise chain
和?[then or catch?](http://liubin.github.io/promises-book/#then-or-catch)?中說到的一樣,由Promise內部消化掉的錯誤,隨著調試工具或者類庫的改進,大多數情況下也許已經不是特別大的問題了。
此外,由于?`done`?不會有返回值,因此不能在它之后進行方法鏈的創建,為了實現Promise方法風格上的統一,我們也可以使用`done`方法。
[ES6 Promises](http://liubin.github.io/promises-book/#es6-promises)?本身提供的功能并不是特別多。 因此,我想很多時候可能需要我們自己進行擴展或者使用第三方類庫。
我們好不容易將異步處理統一采用Promise進行統一處理,但是如果做過頭了,也會將系統變得特別復雜,因此,保持風格的統一是Promise作為抽象對象非常重要的部分。
> 在?[Promises: The Extension Problem (part 4) | getiblog](http://blog.getify.com/promises-part-4/)?中,介紹了一些如何編寫Promise擴展程序的方法。
> * 擴展?`Promise.prototype`?的方法
> * 利用 Wrapper/Delegate 創建抽象層
> 此外,關于 Delegate 的詳細使用方法,也可以參考?[Chapter?28.?Subclassing Built-ins](http://speakingjs.com/es5/ch28.html)?,那里有詳細的說明。
- 前言
- 第一章 - 什么是Promise
- 1.1. 什么是Promise
- 1.2. Promise簡介
- 1.3. 編寫Promise代碼
- 第二章 - 實戰Promise
- 2.1. Promise.resolve
- 2.2. Promise.reject
- 2.3. 專欄: Promise只能進行異步操作?
- 2.4. Promise#then
- 2.5. Promise#catch
- 2.6. 專欄: 每次調用then都會返回一個新創建的promise對象
- 2.7. Promise和數組
- 2.8. Promise.all
- 2.9. Promise.race
- 2.10. then or catch?
- 第三章 - Promise測試
- 3.1. 基本測試
- 3.2. Mocha對Promise的支持
- 3.3. 編寫可控測試(controllable tests)
- 第四章 - Advanced
- 4.1. Promise的實現類庫(Library)
- 4.2. Promise.resolve和Thenable
- 4.3. 使用reject而不是throw
- 4.4. Deferred和Promise
- 4.5. 使用Promise.race和delay取消XHR請求
- 4.6. 什么是 Promise.prototype.done ?
- 4.7. Promise和方法鏈(method chain)
- 4.8. 使用Promise進行順序(sequence)處理
- 第五章 - Promises API Reference
- 5.1. Promise#then
- 5.2. Promise#catch
- 5.3. Promise.resolve
- 5.4. Promise.reject
- 5.5. Promise.all
- 5.6. Promise.race
- 第六章 - 用語集
- 第七章 - 參考網站
- 第八章 - 關于作者
- 第九章 - 關于譯者