为了账号安全,请及时绑定邮箱和手机立即绑定

回调地狱【Callback Hell】

标签:
JavaScript

什么是回调地狱?

众所周知,JavaScript因为其天生异步的特性被Node采用为开发语言。异步能够提高IO处理性能,但随之而来也带来一些问题,可能有的人对Node不太了解,这里就以浏览器环境中的JS来阐述。
假设业务开发中有4个接口,每个接口都依赖于前一个接口的返回,即request2依赖request1,request3依赖request2,新手可能会写出如下代码:
回调地狱
运行这段程序,会按顺序输出1,2,3,4;request函数是一个用定时器模拟的ajax请求,因为请求的响应时间是随机的,受各种因素影响,如网络时延,服务器处理的速度等等,所以用定时器+随机时间来模拟这个情况。
输出
可以看到,因为ajax的依赖关系导致一个非常深的回调嵌套,这就是所谓的回调地狱。针对这个情况,有的人会将每个请求封装成一个函数,再在函数中去调用下一个函数,最终代码可能会被改写成如下:
封装成函数
这段代码同样会输出1,2,3,4,而且回调嵌套的情况也有了很大的改善,但是每个函数需要明确知道下一个函数是什么,这违背了程序设计原则中的“最小知识”原则,那么如果想让程序再“优雅”一点该如何改进呢?如果读者了解ES6的话,应该都知道ES6中引入了Promise这个API。


Promise

Promise是ES6标准新增的一个API,意在解决上述的回调问题,并且被广泛使用。使用Promise编写也會提高程序的可读性、可维护性,使用Promise,每个函数调用不需要知道下一个执行程序应该转到哪里,执行顺序完全交给调用方。下面我们使用Promise来改写:
Promise
这里,我们让每个函数都返回一个Promise对象,然后在响应中将数据resolve交给写一个函数,而不需要关心下一个函数是什么。而调用则完全交给调用方,这里是按顺序来调用,如果接口逻辑修改,依赖关系变成了1->3->4->2,那么对于我们的程序而言,只需要修改promise的链式调用顺序即可,完全不需要更改函数内部的逻辑,而且这里很明显每个函数之间有非常大的相似之处,实际开发可能不会有这么高的相似度,但如果有,可以继续封装,抽象成一个公共的函数。使用Promise的好处不言而喻,也因此被广泛使用,也为后来的async带来了便利,使用async比输写Promise更加的优雅,就像同步代码一般。不过本文暂不谈论较新的知识点,Promise虽然好,但是仍然有一些浏览器不支持,比如IE,如果要在这些浏览器中使用需要引入polyfill,那么为了兼容性,除了promise还有其他的方法来写出这样“优雅”的代码吗?有,必须有。


队列

队列是一个跟栈差不多概念的东西,只不过栈通常是后进先出【即LIFO】,而队列是先进先出【FIFO】。类比现实场景如银行排队,排在前面的人先予处理,前面的人处理完了再轮到下一个。这种情况刚好有点像上文中几个请求的调用方式。我们可以先将请求按顺序存储在队列中就好比显示中的排队,然后按顺序处理,等到当前处理的请求响应后再去处理下一个。
那么首先,我们应该先实现一个队列,在JS中,数组是被广泛应用的一种数据结构,队列和栈在JS中都可以用数组来模拟:
队列
push方法用来将函数推入队列中,然后也提供了pop,remove,clear的API,不过本文中暂时用不到,因为通常如果要实现一个完整的队列都需要提供这些API,故而就列出来,当然这里因为是作为演示,所以在实现上并不算很完善,如果真需要在生产环境中使用,建议将其实现得更完善,考虑更多的边界条件等等,还要考虑复用,共用一个队列容易造成管理混乱,所以也需要考虑是否包装成类等等。
队列的API和普通数组并无而致,核心区别其实是next函数,next函数主要就是取出队列中的首个函数调用,并将next函数拼接在原有参数之后通过apply调用传入。
为什么是首个函数?因为取出函数执行完之后,函数就应该离开队列了,下一个函数就被推到了首个。
刚刚也提到了next函数会作为最后一个参数传入调用函数,这样封装的函数不需要修改原有的参数规则,只需知道最后一个参数固定是next函数即可。然后在需要转交执行权时调用next函数并将需要传递的数据传入即可,同样无需关心下一个函数是什么,也无需关心是否还有下一个函数。
再来看看函数有什么变化:
队列方案主程序
代码执行同样输出1,2,3,4。队列相比Promise在某种程度上还稍微更简洁一点,而且也保留了同样的可读性和可维护性。这里需要注意的是——程序默认next是必需参数,实际函数内部应该加上next参数的判断,因为函数不一定在队列中调用,可能是单独调用,那么这个next参数可能为空,所以严格上来说应该加上next函数判断:

if(typeof next === 'function') next(res.content);

这样函数才可在程序中单独调用:

req3(2333);

结语

本文主要介绍了如何解决回调地狱,让代码写起来更优雅的一些方案,因为是演示代码,所以还有许多实现不完善的地方,请勿直接copy使用,否则后果自负,建议理解其中的实现思路,自行实现。共勉!!

点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消