原文地址: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