深入理解 Promise 五部曲:2. 控制权转换问题

[转, 原文地址:http://segmentfault.com/a/1190000000591382 ]

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

我们现在要找出一个更好的方式来表达异步流程,然后看看Promises是怎么解决这个问题的。

回调嵌套

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

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

当我们只有一个异步任务的时候使用回调函数看起来还不会有什么问题。但是,实际是我们完成一个任务通常需要多个异步操作。例如:

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的状态必须是可靠并且不可变的。

时间: 2024-10-26 16:39:31

深入理解 Promise 五部曲:2. 控制权转换问题的相关文章

深入理解 Promise 五部曲:4. 扩展问题

[转, 原文地址:http://segmentfault.com/a/1190000000600268 ] 现在,我希望你已经看过深入理解Promise的前三篇文章了.并且假设你已经完全理解Promises是什么以及深入讨论Promises的重要性. 不要扩展原生对象! 回到2005年,Prototype.js框架是最先提出扩展Javascript原生对象的内置prototype属性的框架之一.它们的想法是我们可以通过向prototype属性添加额外的方法来扩展现有的功能. 如果你对近十年Jav

深入理解 Promise 五部曲:5. LEGO

[原文地址: https://segmentfault.com/a/1190000000611040] 在 Part4:扩展问题 中,我讨论了如何扩展和抽象Promise是多么的常见,以及这中间的一些问题.但是为什么promise对于开发者来说不是足够友好的呢?这就是它的设计用意吗? I've Got Friends In Low Places Promise被设计为低级别的构建块.一个promise就像一个乐高玩具.单个乐高只是一个有趣的玩具.但是如果把它们拼在一起,你会感受到更多的乐趣. 问

土豆网启动CATCH五部曲引领网络视频娱乐营销新趋势

2009年11月,一位大学在读女学生从全国52万参赛选手中脱颖而出,在土豆网与诺基亚联合举办的财智真人秀节目<互联网百万富翁>活动中捧走主办方土豆网和诺基亚联合提供的百万现金大奖,超过4600万人次在线观看了7场决赛,这也成为了诺基亚2009年最大一轮的网络投放和当年最成功的网络整合营销活动.2010年春夏,一个名为"趣喝美汁源.一笑赢千金"的活动风靡网络,根据第三方数据公司艾瑞的研究报告显示,这一活动在网民中的知名度达到31.9%,"趣喝美汁源.一笑赢千金&qu

论坛建设五部曲之第二部:基础篇

上个星期的<论坛建设五部曲之第一部:主题篇>得到了很多朋友的支持.大家也都就文中的观点提出了自己的见解.其中"巡弋流星"提到:"创建一个有人气的论坛是不容易的,不管是你的定向如何,面向什么样的群体也好,只有唯一的根本'遵循以人为本的原则'".确实是的,无论是建设何种形式的论坛,最终所面对的还是人. 非常的感谢大家的热情参与与讨论,对破帽的启发也很多.在上一篇文章中,破帽遗漏了非常重要的一点:无论在任何时候,都不要奢望做一些和国家法律相抵触的主题和内容.不

吐血分享网站优化五部曲

每位SEOER在优化网站的时候,虽然之前都有学过很多关于SEO的技术,但是当真正接收网站的时候,就不知道该如何入手去优化,SEO细节非常多,不知道该先优化哪个部分,这里就总结一下我个人的优化经验. 一:关键词优化 关键词可以算是整个网站中的灵魂了,前期如果是关键词选择错误了,那么后面所做的工作都是徒劳无功的,所以定位网站关键词是最重要的一步,这里可以借助第三方工具.例如百度相关搜索.百度指数.谷歌关键词工具等. 二:网站内部结构 网站内部结构包括了模板代码.URL链接.各个版面的文章调用.相关链

Eric S. Raymond 五部曲

    推荐一个项目:http://code.google.com/p/master-zhdoc/     这个项目的目的是整理翻译一些大师的名篇.     现在已经完成<Eric S. Raymond 五部曲>的翻译,包括<Hacker文化简史>.<大教堂和集市>.<如何成为一名Hacker>.<开拓智域>和<魔 法大锅炉>.有兴趣的可以在这里下载. 文章转自庄周梦蝶  ,原文发布时间 2008-10-31

论坛建设五部曲之第三部:推广篇

中介交易 SEO诊断 淘宝客 云主机 技术大厅 前言:非常抱歉,由于前段时间春节放假,回来之后又有很多事情忙的不可开交,<论坛建设五部曲>也因此搁浅至今.现在也是抽出一点时间完成第三部,其中的一些观点也可能有失偏颇,不足之处还请大家多多发言,交流,共同进步. ――――― 破帽遮颜 前面和大家探讨过<论坛建设五部曲之第一部:主题篇>和<论坛建设五部曲之第二部:基础篇>,接下来要和大家讨论一下<论坛建设五部曲之第三部:推广篇>.论坛的推广无可非议,让众多的站长朋

超灵验的投资心理五部曲

投资人问: 每次相中一只个股,接着自然会观察股价.麻烦的是,它若下跌,一定暗自庆幸没买.当它上涨,反而开始心动,几天之后忍不住去追,总是买在最高点.问题究竟是出在什么地方呢? 投资心理五部曲 投资人在考虑买进一只股票时,会经过5种不同阶段的心路历程,所谓的投资心理五部曲分别是:酸溜溜.失望.生气.疯狂与反过来认同.如果不能在第二部曲之前及时进场买进,则千万不可再眷恋不舍,忘了它吧! 在众多投资悲剧中,"盲目追高价"永远是排名第一的祸首.许多投资人常常因为缺乏定力,在明知已经丧失了进场良

七匹狼电商的五部曲:从军团营销作战到O2O

看七匹狼如何从军团营销作战到O2O的联动,逐一实现自己的电子商务战略. 厦门的土壤正在成为继杭州.北京.深圳等电商圈之外的又一高地,这个全岛覆盖3G网络的地区正在快速涌现出一批电子商务的实践者. 这家拥有3000多家线下实体店铺,曾经在电子商务上小心谨慎的传统企业福建七匹狼实业股份有限公司(以下简称"七匹狼"),从去年开始,电子商务业务保持了300%以上的增长.据福建七匹狼实业股份有限公司董事长周少雄预计,今年在线零售收入占比将达到10%.按照该公司去年21亿元的收入规模推算,电商业务