原文地址:http://blog.getify.com/promis...javascript
若是你須要遇上咱們關於Promise的進度,能夠看看這個系列前兩篇文章深刻理解Promise五部曲--1.異步問題和深刻理解Promise五部曲--2.控制權轉移問題。java
在前面,咱們說明了幾個關於Promises如何工做的要點,這些要點是咱們之因此能夠信任promise機制做爲控制轉移的一種解決方案的基礎。git
這些要點直接來自Promises/A+規範。任何本地實現或者polyfill或者庫都必須經過一個全面嚴格的測試來肯定是否符合規範。github
對於promises可靠性是最基本的,由於若是沒有可靠性,那麼你就跟使用普通的回調同樣了。你必須謹慎地編寫那些涉及到異步調用第三方庫的代碼。你必須本身來解決狀態跟蹤的問題而後確保第三方庫不會出問題。segmentfault
若是沒有可靠的promises你本身能夠完成異步任務嗎?固然能夠。可是問題是,你本身沒法處理得很完美,你得把不少額外的變量加到你的代碼中而且你會產生一個將來的維護風險,代碼會變得很難維護。數組
Promises是被設計用來規範和集中這種邏輯的。你可使用一個規範的promise系統而不用擔憂可靠性問題,由於它會按照Promises機制來執行。promise
在理論上這個可靠性保證合同聽起來很棒。可是在JavaScript中真的有可能有這麼一個機制嗎?瀏覽器
在我開始說這個問題以前,咱們首先排除一些JS代碼中的可靠性問題:安全
咱們這裏的討論跟密碼/加密中的「私有性」和「安全」無關。服務器
和JS代碼能夠被用戶經過查看源碼看到無關。
和一個黑客能夠侵入你的服務器來發送一些惡意代碼或者經過中間人攻擊來劫持瀏覽器和服務器之間的鏈接來實現一樣的目的或者甚至在運行時使用XSS漏洞來注入惡意代碼無關。
同時,也和惡意代碼一旦存在你的頁面就能夠理論上修改JavaScript運行時功能(好比經過修改Object.prototype
或者Function.prototype
)來破壞你的程序這個事實無關。
類似的,和一些粗心的代碼可能會意外地經過非標準的方式來修改標準JS函數無關。
最後,和若是你頁面中依賴於第三方庫那麼他們的服務器,鏈接和代碼也會出現上面所說的漏洞無關。
如今我能夠繼續了,可是我認爲你已經找到關鍵點了。咱們在經過一個假設來縮小咱們的討論範圍:當全部的代碼以及主機環境都在一種預期的安全的狀態中時,你的程序會如何執行?
這並非說咱們使用Promise所作的事情對上面這些問題沒有幫助。這僅僅是因爲這些問題在一個更高的層面上---這些問題遠離了編寫API和模式,這些問題留給專家來討論。
咱們看看下面這個例子:
var myPromise = { state: { status: 1, value: "Hello World" }, then: function(success,failure) { // implement something like a thenable's behavior } };
我能夠新建一個像這樣的對象,而後在平時使用它而且說我在用promises。實際是,我能夠再完善一下使它能夠經過整個Promises/A+ 測試網站的測試。
你如何回答這個問題比你意識到的更重要。在不少開發者社區中不少人的回答是,是的。
我很肯定的說,不是!
爲何?若是你經過了promises測試網站,那麼它就是一個promise 了,不是嗎?並且,它在全部狀況下都按照規範來執行,不是嗎?
promises的精髓遠不是規範說的那麼簡單,是可靠性。
可靠性是一個promise就是一個狀態(狀態會從"pending"轉變成"resolved"或者"rejected"其中一個)的容器,這些狀態會附帶一個結果值(成功信息或者錯誤信息)。可靠性是一旦一個promise的狀態變爲"resolved"或者"rejected",那麼就不能改變也不會改變。可靠性就是完成的promise是不可變的。
可是promises的精髓還有一些更深層次的東西,這些是沒法經過閱讀規範看出來的:改變一個promise狀態和設置它的完成值的能力只存在於原始的promise的實現。也就是說這個能力的實現掌握在開發者手裏。
規範的早期版本中,把resolve/reject的功能分離出來放在一個對象中,叫作Deferred。把這想成一個對象對:在建立的時候,咱們建立一個promise和一個deferred,deferred能夠resolve這個promise。重要的是,這兩個能夠被分開,一部分代碼能夠resolve/reject一個promise而另一部分只能監聽這個變化而後作出迴應。
規範的後續版本中簡化了promises,經過刪除deferred對象,取而代之的是簡單的暴露出原來屬於deferred的resolve()
和reject()
方法。
var p = new Promise( function(resolve,reject){ // I have `resolve()` and `reject()` from the // hidden `deferred`, and I **alone** control // the state of the promise. } ); // now, I can pass around `p` freely, and it can't // be changed by anyone else but the creator.
看看以前的那個myPromise
對象。你注意到了什麼嗎?
var myPromise = { state: { status: 1, value: "Hello World" }, then: function(success,failure) { // implement something like a thenable's behavior } };
若是你處處傳遞myPromise
,而後無論惡意代碼仍是意外的代碼均可以改變myPromise.state.status
或者myPromise.state.value
屬性,咱們是否是開了一個很大的後門,失去了Promises的可靠性。
固然,答案是確定的。把狀態暴露給方法使得這不是一個真正的promise。由於如今promise的保證已經徹底不可靠了。
若是你從一個第三方庫中獲得了一個這樣的對象,你不會信任它的,不是嗎?更重要的,若是你把這個對象傳遞給其餘第三方庫,你確定不會相信只有原始的建立者才能修改它,不是嗎?
固然不會相信。那就太天真了。
你看,使用promises是基於可靠性的。而後可靠性是基於promise的狀態是與外部影響隔離的,只有建立者能改變。注意到我並無說狀態必須是私有的,只要它不會被外界改變就能夠。
若是沒有promise的對象不會被除了建立者改變的可靠性,那麼promise就幾乎失去了它的意義。
注意,這正是事情變得模糊的地方,是不可忽視的事實。
大多數爲了在舊的JS環境下可以支持promise的polyfill會把狀態經過可變的方式暴露出來。
Ouch!!!
在這方面,個人ES6 Promise polyfill"Native Promise Only"沒有把state暴露出來。據我所知,這是惟一一個沒有把promise狀態暴露出來的polyfill。
爲何?由於我不只僅關心Promise規範,我更在乎Promises的精髓。
可是究竟爲何全部這些高度可信的Promise polyfill和庫會忘了promise中這麼重要的東西呢?由於在原生Javascript有一些限制,這是一些內置機制不須要遵循的。
簡單的說,即將到來的ES6標準指出Promise
是一個「class」,因此做爲一個「class」,promise必須能夠被子類化。換句話說,你必須能夠建立一個class CustomPromise extends Promise{..}
子類,在這個基礎上你能夠擴展內置promises的功能。
例如,你須要一個自定義的promise,這個promise能夠處理超過一條消息。至少理論上,實現這個只須要你繼承內置Promise
類而後擴展它。
鑑於我對JS中類概念的偏見,我認爲Promise
子類化是一種沒有意義的鬧劇或者轉移注意力的幌子。我努力讓本身想出一些Promise子類化的好處,但是我實在想不出來。
並且,若是要繼續保持一些特性來遵循Promises/A+ Test Suite,這些子類的實現極可能變得至關笨拙。
最後,我對於promise的子類化沒有任何好感。
不涉及太多JS的細節,把Promise
表達成一個能夠被繼承的"class"須要你把實例方法加入到Promise.prototype
對象中。
可是當你這麼作的時候,你就把then..()
和catch(..)
變成共享方法,全部Promise
實例均可以訪問到,而後這些方法只能經過this訪問每一個實例上的公共屬性。
換句話說,若是要使得promise能夠子類化,只使用簡單的JS是不可能的,必須使用閉包或其餘方法來爲每一個實例建立私有的promise狀態。
我知道如今你已經開始想各類你見過的能夠實現閉包私有和this
公共繼承混合的方法。
我能夠寫一整本書來講明爲何這樣行不通,可是我這裏就簡單的說下:不要管你所聽到的,只使用ES5中可使用的方法,你是不可能建立私有狀態同時又能夠有效子類化的promise。
這兩個概念在ES5如下是互相排斥的。
另外一個ES6中的新特性是WeakMap。簡單的說,一個WeakMap
實例可以使用對象引用做爲鍵,而後和一個數據相聯繫,而不須要真正把數據存儲在對象上。
這正是咱們須要的,不是嗎?咱們須要一個咱們公共的then(..)
和catch(..)
能夠訪問的WeakMap
,不管this
綁定的是什麼,它們均可以根據this
訪問到而且查找對應的被保護的狀態值。這個特權Promise
方法能夠取得這個內部狀態,可是外部不能。
不過,事情並無這麼美好:
WeakMap
根本不可能經過原生JS用性能可接受的方法實現。
就算咱們在ES5及如下可使用WeakMap
,它仍是沒有徹底解決子類化的問題,由於你必須隱藏WeakMap
實例使得只有你的Promise
方法能夠訪問,可是這樣的話另外一個Promise
的子類也能訪問到。
假設咱們能夠解決第二個問題---其實咱們不能,就作一個假設。那麼WeakMap
的實現應該是什麼樣的呢?
var WeakMap = function(){ var objs = [], data = []; function findObj(obj) { for (var i=0; i<objs.length; i++) { if (objs[i] === obj) return i; } // not found, add it onto the end objs.push( obj ); data.push( undefined ); return i; } function __set(key,value) { var idx = findObj( key ); data[idx] = value; } function __get(key) { var idx = findObj( key ); return data[idx]; } return { "set": __set, "get": __get }; }; var myMap = new WeakMap(); var myObj = {}; myMap.set( myObj, "foo" ); myObj.foo; // undefined myMap.get( myObj ); // "foo"
OK,基本的思想就是咱們維護兩個數組(objs
,data
),經過下標相對應。在第一個數組中保存對象引用,在另外一個保存數據。
漂亮,不是嗎?
看看性能怎麼樣吧。看看findObj(..)
,它要循環整個數組來找到相應的數據。引用越多性能就越低。
可是這還不是最壞的地方。WeakMap
之因此叫作「Weak」是因爲垃圾回收行爲。在咱們WeakMap
的實現中,會保存每一個對象的引用,這就意味着就算程序已經沒有對於對象的引用了,這些對象仍是不能被回收。可是真正的WeakMap
就是這麼「weak」,因此你不須要作任何事情來優化垃圾回收。
好的,WeakMap
是一個錯誤的但願。它並無解決ES6中的問題而且使得事情在ES5及如下變得更糟。
這是個問題!
我真的但願我能建立一個忠實的Peomise
polyfill給ES5及如下。可是必須作一個選擇,在這裏出現了一個分歧。要不就放棄子類化的功能,要不就放棄做爲promise的可靠性。
那麼咱們該怎麼作呢?
我會作另外一個promise polyfill,這個polyfill選擇保留子類化的能力,以可變的state爲代價。
我已經選擇了拋棄子類化使得個人promise polyfill能夠很可靠。就像我以前說的,我認爲promise的子類化最終會被證實是一個華而不實的東西。我不會犧牲promise的可靠性來順從子類化。
很顯然,其餘人對於這個問題會有不一樣的見解。可是我只想讓你問問你本身:一個不可靠的promise能夠用來幹嗎?什麼代碼能真正拯救你?什麼代碼能夠作得更好?
現有的Promise polyfill和庫的問題比不可變的state vs 子類化更深層面。在第四部分:擴展問題中,我會指出許多現有polyfill和庫中的問題。
這篇文章不大好翻譯也不大好理解,因此在這裏總結下個人理解,但願對你們的理解有所幫助,若是你們有什麼不一樣的見解,歡迎討論。
這篇文章圍繞Promise的可靠性展開,Promise的可靠性是它的精髓所在。要實現Promise的可靠性最關鍵的就是要保證Promise的狀態值state不能被外部改變,這樣才能保證狀態值的不可逆。
而如今幾乎全部的Promise庫都忽略了這個關鍵,而它們會忽略這個關鍵點一個很重要的緣由就是在ES6的規範中,Promise被規定爲一個類,也就是說Promise是能夠被子類化的。然而在ES5及如下的規範中,在沒有private
關鍵字的狀況下,是不可能實現可子類化同時又能保證Promise的狀態值不會被外部改變(真的嗎?我保持懷疑態度)。而在ES6中出現的新對象WeakMap
確實給實現Promise帶來了新的思路,能夠在ES5及如下環境中實現WeakMap
,利用它的特色能夠實現符合要求的Promise。具體實現思路就是:定義一個全局私有的WeakMap
,這個WeakMap
只有公共的方法then()
和catch()
能夠訪問到,在這個WeakMap
中以每一個Promise實例的this做爲鍵,狀態值state做爲值進行存儲。這樣在每一個Promise實例中均可以經過本身的this對象查找本身的狀態值,而不能查找到其餘Promise實例的狀態值,這樣就實現了狀態值的外部不可修改。可是WeakMap
有一個很大的問題就是性能比較低而且不利於垃圾回收,因此這並非一個理想的解決方案。
綜上兩個緣由就致使瞭如今大部分庫暴露state狀態值,它們爲了實現子類化選擇了暴露狀態值,丟棄了Promise的精髓所在。
而在做者看來子類化對於Promise的重要性遠遠比不上Promise的可靠性,因此它選擇了放棄子類化而保證Promise的可靠性。事實確實是這樣,若是不能保證Promise的可靠性,那麼就會出現第一篇中出現的那個不可靠的狀況,這樣Promise除了改善了回調金字塔的問題,跟普通的回調也就沒有什麼區別了,也就失去了它更重要的意義。
深刻理解Promise五部曲--1.異步問題
深刻理解Promise五部曲--2.轉換問題
深刻理解Promise五部曲--3.可靠性問題
深刻理解Promise五部曲--4.擴展性問題
深刻理解Promise五部曲--5.樂高問題
最後,安利下個人我的博客,歡迎訪問:http://bin-playground.top