[译] 深刻理解 Promise 五部曲:2. 控制权转换问题

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

厦门旅行归来,继续理解Promisejava

在上一篇深刻理解Promise五部曲:1.异步问题中,咱们揭示了JS的异步事件轮询并发模型而且解释了多任务是如何相互穿插使得它们看起来像是同时运行的。而后咱们讨论了为何咱们努力地在咱们的代码里表达这些东西以及为何咱们的大脑不善于理解它们。segmentfault

咱们如今要找出一个更好的方式来表达异步流程,而后看看Promises是怎么解决这个问题的。promise

回调嵌套

JS从一开始就使用事件轮询的并发模型。咱们一直以来都在写异步的程序。直到最近,咱们仍然在用简单的回调函数来处理异步的问题。并发

makeAjaxRequest(url,function(respnose){
    alert("Response:" + response) ;
}) ;

当咱们只有一个异步任务的时候使用回调函数看起来还不会有什么问题。可是,实际是咱们完成一个任务一般须要多个异步操做。例如:app

btn.addEventListener("click",function(evt){
    makeAjaxRequest(url,function(response){
        makeAjaxRequest(anotherURL + "?resp=" + response,function(response2){
            alert("Response2:" + response) ;
        })
    }) ;
},false) ;

把一系列异步操做连接在一块儿最天然的方式就是使用回调嵌套,步骤2嵌套在步骤1中而后步骤3嵌套在步骤2中,等等。异步

回调地狱

你使用越多的回调,就会有越多的嵌套,不断缩进意大利面条似的代码。很显然,这种代码难以编写,难以理解并且难以维护。若是咱们花点时间来理清这些代码每每会让咱们事半功倍。这类嵌套/缩进常常被叫作"回调地狱"。有时也被叫作"回调金字塔",专指因为代码不断缩进所造成的金字塔形状,缩进越多金字塔形状越明显。编辑器

可是我仍是以为"回调地狱"真的跟嵌套和缩进扯不上太大的关系。若是以前有人跟你说回调地狱就是指嵌套和缩进的话,不要相信他,由于他们并不理解回调真正的问题在哪儿。函数

可靠性缺失

回调(不管是否有嵌套)的真正问题是远比编辑器中的空白符严重。让咱们来分析下下面这个简单的回调发生了什么网站

//1.everything in my program before now

someAsyncThing(function(){
    //2.everything in my program for later
}) ;

你看清这段代码说了什么吗?你从根本上把你的程序分红了两个部分:

  1. 直到如今为止发生的事情

  2. 之后会发生的事情

换句话说,你把第二部分代码包装在一个回调函数中而后延迟到后面执行。

可是这并非问题,真正问题是在1和2之间发生了什么。请问在这段时间内是谁在控制这些。
someAsyncThing(..)控制着这些。是你本身拥有并管理someAsyncThing()吗?许多时候不是。更重要的是,你有多信任someAsyncThing(..)?你会问,信任什么?无论你意识到没有,你潜在的相信someAsyncThing(..)会作到下面这些:

  1. 不会太早调用个人回调函数

  2. 不会太迟调用个人回调函数(1,2就是说会在适当的时候调用回调函数)

  3. 不会调用个人回调太少次(不会少于实际应该调用的次数,好比不会漏掉函数调用)

  4. 不会调用个人回调太屡次(不会多于实际应该调用的次数,好比重复调用)

  5. 会给个人回调提供必要的参数

  6. 在个人回调失败的时候会提醒我

咳!你也太信任它了!

实际上,这里真正的问题是因为回调引发的控制转移。在你的程序的前半部分,你控制着程序的进程。如今你转移了控制权,someAsyncThing(..)控制了你剩余程序何时返回以及是否返回。控制转移代表了你的代码和其余人的代码之间的过分信任关系。

恐吓战术

someAsyncThing(..)是第三方库的一个方法而且你没法控制不能检查的时候会发生什么?只能祝你好运了!

好比你有一个电子商务网站,用户就要完成付款的步骤了,可是在扣费以前有最后一个步骤,它须要通知一个第三方跟踪库。你调用他们API,而且提供一个回调函数。大部分状况下,这不会有什么问题。可是,在此次业务中,有一些你和他们都没有意识到的奇怪的Bug,结果就是第三方库在超时以前五秒的时间内每隔一秒就会调用一次回调函数。猜猜发生了什么?在这个回调里调用了chargeTheCreditCard()

Oops,消费者被扣了五次钱。为何?由于你相信第三方库只会调用你的回调一次。

因此你不得不被丢鸡蛋而且给消费者道歉归还多扣的四次钱。而后你马上采起措施确保这种状况不会再发生。你会怎么作呢?

你可能会建立一些状态值来跟踪你的回调,当它被调用一次以后会被标记,而后就能够忽略任何意外的重复调用。不管第三方如何道歉而且承诺他们的bug已经修复了,你不再会相信他们了,不是吗?

这看起来像一个愚蠢的场景,可是这可能比你想得还广泛。咱们的程序变得越复杂,咱们就会集成越多的第三方/外部代码,这种愚蠢的场景就越容易发生。

布基胶带

你给你的回调加入了状态跟踪机制,而后睡了一个好觉。可是实际上你只是处理了信任列表许多项目中的一项。当另外一个bug形成另外一个可靠性丢失的状况时会发生什么?更多的改造,更多丑陋的代码。

更多布基胶带。你必须不断修复回调中的漏洞。不管你是多优秀的开发者,不管你的布基胶带多漂亮,事实就是:在你信任墙上的回调充满了漏洞。

Promise解决方案

一些人喜欢使用布基绷带而且给信任墙上的洞打补丁。可是在某些时候,你也许会问本身,是否有其余模式来表达异步流程控制,不须要忍受全部这些可靠性丢失?

是的!Promises就是一个方法。

在我解释它们是怎么工做以前,让我来解释一些它们背后的概念问题。

快餐业务

你走进你最喜好的快餐店,走到前台要了一些美味的食物。收银员告诉你一共7.53美圆而后你把钱给她。她会给回你什么东西呢?

若是你足够幸运,你要的食物已经准备好了。可是大多数状况下,你会拿到一个写着序列号的小票,是吧?因此你站到一边等待你的食物。

很快,你听到广播响起:“请317号取餐”。正好是你的号码。你走到前台用小票换来你的食物!谢天谢地,你不用忍受太长的等待。

刚才发生的是一个对于Promises很好的比喻。你走到前台开始一个业务,可是这个业务不能立刻完成。因此,你获得一个在迟些时候完成业务(你的食物)的promise(小票)。一旦你的食物准备就绪,你会获得通知而后你第一时间用你的promise(小票)换来了你想要的东西:食物。

换句话说,带有序列号的小票就是对于一个将来结果的承诺。

完成事件

想一想上面调用someAsyncThing(..)的例子。若是你能够调用它而后订阅一个事件,当这个调用完成的时候你会获得通知而不是传递一个回调给它,这样难道不会更好吗?

例如,想象这样的代码:

var listener = someAsyncThing(..) ;
listener.on("completion",function(data){
    //keep going now !
}) ;

实际上,若是咱们还能够监听调用失败的事件那就更好了。

listener.on("failure",function(){
    //Oops,What's plan B?
}) ;

如今,对于咱们调用的每一个函数,咱们可以在函数成功执行或者失败的时候获得通知。换句话说,每一个函数调用会是流程控制图上的决策点。

Promise"事件"

Promises就像是一个函数在说“我这有一个事件监听器,当我完成或者失败的时候会被通知到。”咱们看看它是怎么工做的:

function someAsyncThing(){
    var p = new Promise(function(resolve,reject){
        //at some later time,call 'resolve()' or 'reject()'
    }) ;
    return p ;
}
var p = someAsyncThing() ;
p.then(
    function(){
        //success happened    
    },
    function(){
        //failure happened
    }
) ;

你只须要监听then事件,而后经过知道哪一个回调函数被调用就能够知道是成功仍是失败。

逆转

经过promises,咱们从新得到了程序的控制权而不是经过给第三方库传递回调来转移控制权。这是javascript中异步控制流程表达上一个很大的进步。

“等等”,你说。“我仍然要传递回调啊。有什么不同?!”嗯。。。好眼力!

有些人声称Promises经过移除回调来解决“回调地狱”的问题。并非这样!在一些状况下,你甚至须要比之前更多的回调。同时,根据你如何编写你的代码,你可能仍然须要把promises嵌套在别的promises中!

批判性地看,promises所作的只是改变了你传递回调的地方。

本质上,若是你把你的回调传递给拥有良好保证和可预测性的中立Promises机制,你实质上从新得到了对于后续程序能很稳定而且运行良好的可靠性。标准的promises机制有如下这些保证:

  1. 若是promise被resolve,它要不是success就是failure,不可能同时存在。

  2. 一旦promise被resolve,它就不再会被resolve(不会出现重复调用)。

  3. 若是promise返回了成功的信息,那么你绑定在成功事件上的回调会获得这个消息。

  4. 若是发生了错误,promise会收到一个带有错误信息的错误通知。

  5. 不管promise最后的结果是什么(success或者failure),他就不会改变了,你老是能够得到这个消息只要你不销毁promise。

若是咱们从someAsyncThing(..)获得的promise不是可用的标准的promise会发生什么?若是咱们没法判断咱们是否可相信它是真的promise会怎么样?

简单!只要你获得的是“类promise”的,也就是拥有then(..)方法能够注册success和failure事件,那么你就可用使用这个“类promise”而后把它包装在一个你信任的promise中。

var notSureWhatItIs = someAsyncThing();

var p = Promise.resolve( notSureWhatItIs );

// now we can trust `p`!!
p.then(
    function(){
        // success happened 
    },
    function(){
        // failure happened 
    }
);

promises的最重要的特色就是它把咱们处理任何函数调用的成功或者失败的方式规范成了可预测的形式,特别是若是这个调用实际上的异步的。

在这个规范过程当中,它使咱们的程序在可控制的位置而不是把控制权交给一个不可相信的第三方。

总结

不要管你所听到的,“回调地狱”不是真的关于函数嵌套和它们在代码编辑器中产生的缩进。它是关于控制转移的,是指咱们因为把控制权交给一个咱们不能信任的第三方而产生的对咱们的程序失去控制的现象。

Promises逆转了这个状况,它使得咱们从新得到控制权。相比传递回调给第三方函数,函数返回一个promise对象,咱们可使用它来监听函数的成功或失败。在promise咱们仍然使用回调,可是重要的是标准的promise机制使咱们能够信任它们行为的正确性。咱们不须要想办法来处理这些可靠性问题。

在第三部分:可靠性问题中,我会说道一个promises可靠性机制中很特别的部分:一个promise的状态必须是可靠而且不可变的。

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

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

相关文章
相关标签/搜索