3.2 具有 then 方法的鸭子类型
在 Promise 领域,一个重要的细节是如何确定某个值是不是真正的 Promise。或者更直接地说,它是不是一个行为方式类似于 Promise 的值?
既然 Promise 是通过 new Promise(..) 语法创建的,那你可能就认为可以通过 p instanceof Promise 来检查。但遗憾的是,这并不足以作为检查方法,原因有许多。
其中最主要的是,Promise 值可能是从其他浏览器窗口(iframe 等)接收到的。这个浏览器窗口自己的 Promise 可能和当前窗口 /frame 的不同,因此这样的检查无法识别 Promise 实例。
还有,库或框架可能会选择实现自己的 Promise,而不是使用原生 ES6 Promise 实现。实际上,很有可能你是在早期根本没有 Promise 实现的浏览器中使用由库提供的 Promise。
在本章后面讨论 Promise 决议过程的时候,你就会了解为什么有能力识别和判断类似于 Promise 的值是否是真正的 Promise 仍然很重要。不过,你现在只要先记住我的话,知道这一点很重要就行了。
因此,识别 Promise(或者行为类似于 Promise 的东西)就是定义某种称为 thenable 的东西,将其定义为任何具有 then(..) 方法的对象和函数。我们认为,任何这样的值就是 Promise 一致的 thenable。
根据一个值的形态(具有哪些属性)对这个值的类型做出一些假定。这种类型检查 (type check)一般用术语鸭子类型 (duck typing)来表示——“如果它看起来像只鸭子,叫起来像只鸭子,那它一定就是只鸭子”(参见本书的“类型和语法”部分)。于是,对 thenable 值的鸭子类型检测就大致类似于:
if ( p !== null && ( typeof p === "object" || typeof p === "function" ) && typeof p.then === "function" ) { // 假定这是一个thenable! } else { // 不是thenable }
除了在多个地方实现这个逻辑有点丑陋之外,其实还有一些更深层次的麻烦。
如果你试图使用恰好有 then(..) 函数的一个对象或函数值完成一个 Promise,但并不希望它被当作 Promise 或 thenable,那就有点麻烦了,因为它会自动被识别为 thenable,并被按照特定的规则处理(参见本章后面的内容)。
即使你并没有意识到这个值有 then(..) 函数也是这样。比如:
var o = { then: function(){} }; // 让v [[Prototype]]-link到o var v = Object.create( o ); v.someStuff = "cool"; v.otherStuff = "not so cool"; v.hasOwnProperty( "then" ); // false
v 看起来根本不像 Promise 或 thenable。它只是一个具有一些属性的简单对象。你可能只是想要像对其他对象一样发送这个值。
但你不知道的是,v 还 [[Prototype]] 连接(参见《你不知道的 JavaScript(上卷)》的“this 和对象原型”部分)到了另外一个对象 o ,而后者恰好具有一个 then(..) 属性。所以 thenable 鸭子类型检测会把 v 认作一个 thenable。
甚至不需要是直接有意支持的:
Object.prototype.then = function(){}; Array.prototype.then = function(){}; var v1 = { hello: "world" }; var v2 = [ "Hello", "World" ];
v1 和 v2 都会被认作 thenable。如果有任何其他代码无意或恶意地给 Object.prototype 、Array.prototype 或任何其他原生原型添加 then(..) ,你无法控制也无法预测。并且,如果指定的是不调用其参数作为回调的函数,那么如果有 Promise 决议到这样的值,就会永远挂住!真是疯狂。
难以置信?可能吧。
但是别忘了,在 ES6 之前,社区已经有一些著名的非 Promise 库恰好有名为 then(..) 的方法。这些库中有一部分选择了重命名自己的方法以避免冲突(这真糟糕!)。而其他的那些库只是因为无法通过改变摆脱这种冲突,就很不幸地被降级进入了“与基于 Promise 的编码不兼容”的状态。
标准决定劫持之前未保留的——听起来是完全通用的——属性名 then 。这意味着所有值(或其委托),不管是过去的、现存的还是未来的,都不能拥有 then(..) 函数,不管是有意的还是无意的;否则这个值在 Promise 系统中就会被误认为是一个 thenable,这可能会导致非常难以追踪的 bug。
我并不喜欢最后还得用 thenable 鸭子类型检测作为 Promise 的识别方案。还有其他选择,比如 branding,甚至 anti-branding。可我们所用的似乎是针对最差情况的妥协。但情况也并不完全是一片黯淡。后面我们就会看到,thenable 鸭子类型检测还是有用的。只是要清楚,如果 thenable 鸭子类型误把不是 Promise 的东西识别为了 Promise,可能就是有害的。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论