谈谈回调地狱

我是一个前端小透明,在看了一些书和视频后,整理了一下自己的想法,发出来与大家分享。如有错漏,欢迎指正。

一、为什么需要异步?

众所周知,JavaScript在浏览器中主要有两个重要的功能,一是与用户交互,二是发起网络请求。与用户交互需要监听事件,事件随时可能发生,而发起网络请求需要比较长的时间。如果发起网络请求的动作是同步的,那么就会有一段较长的时间,JavaScript完全在发起网络请求或等待回应,没有办法兼顾与用户的交互,这个时候用户就会感觉网页卡顿,用户体验是极差的。

所以需要让发起和接收网络请求变成异步的,使用事件轮询机制,同步代码执行完后,还要进行排队,有空的时候才会把异步的代码执行完。那么问题又来了,要用什么方案实现异步?

二、回调函数

回调函数是实现异步最基本的方案,它是接受完响应数据并且从事件轮询队列轮到才会执行的函数,既能执行函数,实现我们的业务逻辑,而且又不会打扰到监听事件的同步代码。

因此,可以用回调函数的方式实现异步,而且很方便。尤其是在不需要发起连续网络请求的时候,没有任何问题。如果连续地发起了多个网络请求呢?这些异步的回调函数的执行顺序无法保证,如果需要保证执行顺序,只能把下一次发送请求的函数写到回调函数里面去,因此需要层层嵌套,出现了回调地狱的问题。

三、回调地狱

我在《你不知道的JavaScript》看到这样一句话“回调函数是实现异步的基本方式,但并不是唯一甚至不是最好的方式”。回调地狱就是回调函数的一个大坑。有一些老师讲课的时候,说回调地狱是因为嵌套的层次过多造成的,代码看起来不雅观,耦合度高,维护起来困难。这是一个很表面的说法,也是广为人知的一种说法。《你不知道的JavaScript》对回调地狱做了比较深刻的分析和解读,认为问题“根本不是出在嵌套过多上”

我现在用自己的话来讲述一下这本书认为回调地狱的问题。问题有两点。

第一,回调函数的执行是非顺序性的,与我们大脑的正常思维习惯不符合。

什么意思?如果让我们计划一下明天的要做的事情,会是怎样的?一般来说,可能是这样——“早上7点起床,7点半吃早餐,8点读报纸或书,8点半出门,然后坐公交车,到公司上班……”尽管实际情况可能出现各种意外,我们大脑的计划却是按顺序来的,从早到晚。这是我们大脑的顺序性思维。而我们的回调函数则是事件驱动的,如“点击事件”,“定时器到时的事件”,“请求完成的事件”,发现时间是不确定的,就像我们接电话一样,是在发生了“电话铃响”的事件。

假如我们的大脑向执行异步代码那样思考,会是一个怎样的画风?假如我们大脑对明天的计划是这样的:“我走在上班的路上,肯定会接到妈妈打来的电话。她询问我有没有买好要带回家给奶奶的礼物,我发现我忘了,所以马上转身,走进附近的超市购买……”好家伙,上帝视角、未卜先知啊!这里提到了两个事件:“妈妈给我打电话”和“发现我忘了买礼物”,这两个事情发生在“明天”,还是偶然发生的,正常情况我怎么可能会这么思考呢?我们的异步代码就是这样,准备了初始情况“我正走在上班路上”的代码,马上紧接着就是“我接到了妈妈打来的电话之后的做法”,准备好了之后,执行代码,“我走在上班的路上“,然后我走着走着,“接到了妈妈的电话”,这时又“折回去,走到处理接电话的地方”,显然,这里的“折回去”并不是顺序性的,这样来来回回的看代码,会让我们的大脑感觉不舒服。

第二,回调函数有信任的问题

如果说,上一个问题可能只是让我们不舒服,可以通过大量编写代码的经验来克服这种不舒服,那么,现在,你就会看到真正致命的问题了。我们很可能在项目中使用到第三方的API,我们准备好回调函数,有的时候就是让第三方API来调用的。那么问题来了,第三方真的可靠吗?

《你不知道的JavaScript》这本书里举了这样的一个例子:假如你现在做的是一个商城的项目,里面有一个很重要的功能,那就是支付功能,这个功能你在实现的时候是写在了让第三方调用的回调函数里面。你使用了这个第三方的服务,让他们的代码来调用你的回调函数,就万事大吉了吗?可能在几天后,客户来投诉你了,因为他买了个东西,却付了5次钱。你感到一头雾水,你去找那个第三方理论,对方研究之后告诉你,他们修改了代码,一位工作人员错误的把测试代码提交过来了,这个测试代码会反复调用5次回调函数。对此你可以进行判断,如果回调函数调用过一次了,那就不该让它继续执行,来保证只会支付一次。那么问题又来了,假如第三方又改了代码,而这次根本没有调用你的回调函数根本没有支付,这你改怎么判断呢?或者在你还没有准备好的时候,第三方就调用了回调函数,开始支付,这又该怎么办?

如果说“他人即地狱”,那么是否也可以说“他人的代码即地狱”?第三方的代码提供了某项服务,不需要我们在这个环节操心了,同时我们也失去了对这个环节的掌控。第三方的代码像个“黑盒”,我们不知道里面具体做了些什么,更没办法控制它,假如它干了些违背我们本意的事情,我们束手无策。

综上所述,非顺序性缺乏信任才是回调地狱的两大致命问题,远远比看起来的层层嵌套要致命得多。

四、Promise

让我非常惊喜的是,ES6把Promise变成了一个标准化的API,可以使用到Promise简直太幸运了。

首先,promise在使用的时候,把产生结果处理结果的代码分开了,不至于耦合在一起,结构比较清晰。(前者位于Promise构造函数传入的函数,后者位于then和catch的回调函数里)

其次,它真正解决了回调地狱的两个问题。显然,promise并没有完全摆脱掉回调函数,在then和catch的参数中,仍然在使用回调函数。为什么说它解决了回调地狱的两个问题呢?

第一,产生结果的代码写在了Promise构造函数传入的函数中,而处理结果的逻辑却在then方法里面的回调函数里,then方法的返回值也是promise,可以链式调用,这样不仅代码看起来是顺序性的,也保证了执行这些异步代码的时候是顺序性的,解决了回调函数非顺序性的问题

第二,Promise通过权力反转的方式,解决了缺乏信任的问题。假如我们的回调函数是让第三方调用的话,那么等于我们把权力反转给了第三方,什么时候调用,调用几次,是否调用,这些成了第三方有权控制的事情。假如第三方是一个返回promise的API,那么我们就不是让对方来调用回调函数了,而是在对方得到结果后收到一个通知,然后我们自己来调用自己的回调函数,把权力反转回来了。此外,Promise有两个特性来证明它确实是可靠的——①每个Promise都有决议结果,如果是成功的,会得到决议值,如果被拒绝了,会得到错误信息,这会保证回调函数肯定会有一个被调用。②一个promise只能决议一次,决议结果不能改变,决议后可以查看多次。这会保证每个Promise的回调函数只有其中一个被调用,且只会被调用一次,决议结果不可能被恶意代码修改。

如果Promise用起来还不是很让人习惯,看起来不太像同步代码的话,那么ES7提供的异步函数简直是太香了。

结语:

异步是JavaScript必须实现的东西,而回调函数是实现异步的一个基本方法,但回调函数可能导致回调地狱,回调地狱有非顺序性和缺乏信任的问题。Promise以及使用Promise相关的技术可以完美解决这两个问题。

希望这篇文章能够帮助到学习到JavaScript异步的小伙伴们。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值