[译] 深刻理解 Promise 五部曲:4. 扩展问题

原文地址:http://blog.getify.com/promis...javascript

如今,我但愿你已经看过深刻理解Promise的前三篇文章了。而且假设你已经彻底理解Promises是什么以及深刻讨论Promises的重要性。java

不要扩展原生对象!

回到2005年,Prototype.js框架是最早提出扩展Javascript原生对象的内置prototype属性的框架之一。它们的想法是咱们能够经过向prototype属性添加额外的方法来扩展示有的功能。git

若是你对近十年Javascript编程作一个简单的调查,好比使用google简单搜索下,你会发现对于这个想法有不少反对意见。它们都是有缘由的。github

大多数开发者会告诉你:“不要扩展原生对象”或者“只在polyfill的时候扩展原生对象”。后者意味着只有当扩展的功能已经被列入规范而后你只是为了能在旧的环境中使用这些功能的时候才能对元素对象进行扩展。ajax

数组Push方法

想象一个真实的场景(确实发生在我身上):回到Netscape3/4和IE4的时代,当时的JS并无如今这么友好。做为许多显著差别中的一个,数组并无push(..)方法来向它的尾部添加元素。编程

因此,一些人会经过下面这段代码来扩展:segmentfault

//Netscape 4 doesn't hava Array.push
Array.prototype.push = function(elem){
    this[this.length - 1] = elem ;
}

乍一看可能以为没问题,可是你很快就会发现一些问题。数组

  1. 这里须要一个if来判断原生是否有对于push(..)的支持,若是有咱们就可使用原生的push(..)方法。promise

  2. 咱们须要注意咱们已经破坏了数组对象for..in循环行为,由于咱们的push(..)方法会出如今for..in循环的结果中。固然,你不该该在数组上使用for..in循环,这又是另一个话题了。安全

有一个和1相关的更大的问题。不只仅是须要一个if判断:

if(!Array.prototype.push){
    //make our own
}

咱们应该问问咱们本身,若是内置的push(..)实现和咱们的实现不兼容怎么办?若是内置的实现接受不同数量的参数或者不同的参数类型怎么办?

若是咱们的代码依赖于咱们本身实现的push(..),而后咱们只是简单的用新的方法替换咱们本身的方法,那么代码会出现问题。若是咱们的实现覆盖了内置的push(..)实现,而后若是一些JS库指望使用内置的标准push(..)方法怎么办?

这些问题是真实发生在我身上的。我有一个工做是在一个用户的古老的网站上加入一个组件,而后这个组件依赖于jQuery。咱们组件在其余网站均可以正常使用,可是在这个特殊的站点却没法使用。我花了不少时间来找出问题。
最终,我定位到了上面那个if代码片断。这里有什么问题呢?

它的push(..)实现只接受一个参数,然而jQuery中指望是经过push(el1,el2,...)来调用push方法,因此它就没法正常运行了。

Oops。

可是猜猜当我移除原来的push代码时发生了什么?在其余网站这个组件也不能使用的。为何?我仍是不知道具体是为何。我认为他们意外地依赖于外部变量,而这些外部变量没有传递进来。

可是,真正的问题是,有人经过一种对于将来存在潜在危险的方式扩展内置原生对象,致使这个方法在将来可能没法正常运行。

我不是惟一遇到这个问题的人。成千上万的开发者都遇到了这种状况。咱们中的大多数认为你必须十分当心当你扩展原生JS对象时。若是你这么作了,你最好不要使用跟语言新版本中的方法名相同的名字。

Promise扩展

为何全部的老爷爷抱怨现在Promises的火热呢?由于那些开发Promise"polyfills"的人彷佛忘记或者抛弃了老人们的智慧。他们开始直接往PromisePromsie.prototype上加额外的东西。

我真的须要再去解释为何这是一个“将来的”坏点子吗?

Blue In The Face

咱们能够一直争论这个问题到死,可是仍然不能改变这个事实。若是你扩展原生对象,你就是和将来敌对的,就算你以为你本身已经作得很好了。

并且,你用越大众化的名字来扩展原生对象,你越有可能影响将来的人。让咱们看看Bluebird库,由于它是最流行的Promisepolyfill/库之一。它足够快可是它跟其余库比起来也更加大。

可是速度和大小并非我如今担忧的。我关心的是它选择了把本身添加到Promise的命名空间上。就算它使用一个polyfill安全的方式,实际上并无,事实就算它添加许多额外的东西到原生对象上。

例如,Bluebird添加了Promise.method(..):

function someAsyncNonPromiseFunc() {
    // ...
}

var wrappedFn = Promise.method( someAsyncNonPromiseFunc );

var p = wrappedFn(..);

p.then(..)..;

看起来没什么问题,是吗?固然。可是若是某天规范须要添加Promise.method(..)方法。而后若是它的行为和Bluebird有很大的区别会怎么样呢?

你又会看到Array.prototype.push(..)同样的状况。

Bluebird添加了许多东西到原生的Promise。因此有不少可能性会在将来会发生冲突。我但愿我历来不须要去修复某我的的Promise扩展代码。可是,我极可能须要这么作。

将来约束

可是这还不是最糟的。若是Bluebir很是流行,而后许多现实中的网站依赖于这么一个扩展,忽然一天TC39协会经过某种方式强制避免扩展官方规范,那么这些依赖于扩展的网站都将崩溃。

你看,这就是扩展原生对象的危险所在:你为了实现你的功能而后扩展原生对象,而后就拍拍屁股把这些烂摊子留给了TC39成员们。由于你愚蠢的决定Javascript的维护者只能选择其余机制。

不相信我?这种状况已经发生不少次了。你知道为何在19年的JS历史中typeof null === "object"这个bug一直没法修复吗?由于太多的代码都依赖于这段代码了。若是他们修复了这个bug,那么结果可想而知。
我真的不想这种事情发生在Promsie身上。请中止经过扩展原生对象来定义Promise polyfill/库。

包装抽象

我认为咱们须要更多不破坏规范的polyfill,像个人"Native Promise Only"。咱们须要良好,稳固,性能优秀可是和标准兼容的polyfill。

特别的,咱们须要它们以便于那些须要扩展promise的人能够在这个包装上进行操做。咱们不该该很容易得到一个Promisepolyfill而后建立咱们本身的SuperAwesomePromise包装在它上面吗?

已经有不少的好例子了,好比Qwhen,我本身也写了一个,叫作asnquence(async + sequence),个人是设计来隐藏promises的,由于promise是低级别的API,因此与其给你一个简单的抽象的东西不如隐藏丑陋的细节。

例如,比较下下面两段代码。

原生Promises:

function delay(n) {
    return new Promise( function(resolve,reject){
        setTimeout( resolve, n );
    } );
}

function request(url) {
    return new Promise( function(resolve,reject){
        ajax( url, function(err,res){
            if (err) reject( err );
            else resolve( res );
        } );
    } );
}

delay( 100 )
.then( function(){
    return request( "some/url" );
} )
.then(
    success,
    error
);

asynquence:

function delay(n) {
    return ASQ( function(done){
        setTimeout( done, n );
    } );
}

function request(url) {
    return ASQ( function(done){
        ajax( url, done.errfcb );
    } );
}

delay( 100 )
.val( "some/url" )
.seq( request )
.then( success )
.or( error );

但愿你可以经过这个简单的例子看出asynquence是如何下降使用promises来表达异步流程的难度的。它在底层实现为你建立promise,它自动把它们链接在一块儿,而后为一样的组合模式提供了简单的语法。

显然,我认为asynquence是很是使人惊奇的。我认为你应该看看一些例子,而后看看你们扩展的插件,这些插件使得它能提供更多的便利。

若是asynquence不是你的菜,那么你能够再寻找一个适合你的好用知名的抽象库。
可是请不要使用那些扩展原生Promise的库。这对于将来不是一件好事。

总结

Promise是使人惊奇的而且它们正在改变许多JS开发者编写和维护一部流程的方式。ES6带来的原生Promise是这个语言一个重大的胜利。为了加速这个胜利的过程,咱们中的许多人开发Promise polyfill和Promise库。

可是不要由于Promise带来的兴奋和喜悦让你忘了一个不能否认的事实:扩展原生对象是一件危险而且充满冒险的事情,并仅仅对于库的做者也包括使用这些库的全部人。

最后,请负有责任感而且使用安全的promise扩展。咱们在未来会感谢你的。

深刻理解Promise五部曲--1.异步问题
深刻理解Promise五部曲--2.转换问题
深刻理解Promise五部曲--3.可靠性问题
深刻理解Promise五部曲--4.扩展性问题
深刻理解Promise五部曲--5.乐高问题

最后,安利下个人我的博客,欢迎访问:http://bin-playground.top

相关文章
相关标签/搜索