一篇文章让你彻底理解回调函数( 四 )


文章插图
 
有同步回调就有异步回调 。
异步回调
不同于同步回调, 当我们调用某个函数A并以参数的形式传入回调函数后,A函数会立刻返回,也就是说函数A并不会阻塞我们的主程序,一段时间后回调函数开始被执行,此时我们的主程序可能在忙其它任务,回调函数的执行和我们主程序的运行同时进行 。
既然我们的主程序和回调函数的执行可以同时发生,因此一般情况下,主程序和回调函数的执行位于不同的线程或者进程中 。

一篇文章让你彻底理解回调函数

文章插图
 
这就是所谓的异步回调,asynchronous callbacks,也有的资料将其称为deferred callbacks ,名字很形象,延迟回调 。
从上面这两张图中我们也可以看到,异步回调要比同步回调更能充分的利用机器资源,原因就在于在同步模式下主程序会“偷懒”,因为调用其它函数被阻塞而暂停运行,但是异步调用不存在这个问题,主程序会一直运行下去 。
因此,异步回调更常见于I/O操作,天然适用于Web服务这种高并发场景 。
回调对应的编程思维模式让我们用简单的几句话来总结一下回调下与常规编程思维模式的不同 。
假设我们想处理某项任务,这项任务需要依赖某项服务S,我们可以将任务的处理分为两部分,调用服务S前的部分PA,和调用服务S后的部分PB 。
在常规模式下,PA和PB都是服务调用方来执行的,也就是我们自己来执行PA部分,等待服务S返回后再执行PB部分 。
但在回调这种方式下就不一样了 。
在这种情况下,我们自己来执行PA部分,然后告诉服务S:“等你完成服务后执行PB部分” 。
因此我们可以看到,现在一项任务是由不同的模块来协作完成的 。
即:
  • 常规模式:调用完S服务后后我去执行X任务,
  • 回调模式:调用完S服务后你接着再去执行X任务,
【一篇文章让你彻底理解回调函数】其中X是服务调用方制定的,区别在于谁来执行 。
为什么异步回调越来越重要在同步模式下,服务调用方会因服务执行而被阻塞暂停执行,这会导致整个线程被阻塞,因此这种编程方式天然不适用于高并发动辄几万几十万的并发连接场景,
针对高并发这一场景,异步其实是更加高效的,原因很简单,你不需要在原地等待,因此从而更好的利用机器资源,而回调函数又是异步下不可或缺的一种机制 。
回调地狱,callback hell有的同学可能认为有了异步回调这种机制应付起一切高并发场景就可以高枕无忧了 。
实际上在计算机科学中还没有任何一种可以横扫一切包治百病的技术,现在没有,在可预见的将来也不会有,一切都是妥协的结果 。
那么异步回调这种机制有什么问题呢?
实际上我们已经看到了,异步回调这种机制和程序员最熟悉的同步模式不一样,在可理解性上比不过同步,而如果业务逻辑相对复杂,比如我们处理某项任务时不止需要调用一项服务,而是几项甚至十几项,如果这些服务调用都采用异步回调的方式来处理的话,那么很有可能我们就陷入回调地狱中 。
举个例子,假设处理某项任务我们需要调用四个服务,每一个服务都需要依赖上一个服务的结果,如果用同步方式来实现的话可能是这样的:
a = GetServiceA();b = GetServiceB(a);c = GetServiceC(b);d = GetServiceD(c);代码很清晰,很容易理解有没有 。
我们知道异步回调的方式会更加高效,那么使用异步回调的方式来写将会是什么样的呢?
GetServiceA(function(a){ GetServiceB(a, function(b){ GetServiceC(b, function(c){ GetServiceD(c, function(d) { .... }); }); });});我想不需要再强调什么了吧,你觉得这两种写法哪个更容易理解,代码更容易维护呢?
有幸曾经维护过这种类型的代码,不得不说每次增加新功能的时候恨不得自己化为两个分身,一个不得不去重读一边代码;另一个在一旁骂自己为什么当初选择维护这个项目 。
异步回调代码稍不留意就会跌到回调陷阱中,那么有没有一种更好的办法既能结合异步回调的高效又能结合同步编码的简单易读呢?
答案是肯定的,后续再详细讲解这一技术 。




推荐阅读