计算机内功心法六10张图就让你彻底理解回调函数
不知你是不是也有这样的疑惑,我们为什么需要回调函数这个概念呢?直接调用函数就可以了?回调函数到底有什么作用?程序员到底该如何理解回调函数?
这篇文章就来为你解答这些问题,读完这篇文章后你的武器库将新增一件功能强大的利器。一切要从这样的需求说起
假设你们公司要开发下一代国民App明日油条,一款主打解决国民早餐问题的App,为了加快开发进度,这款应用由A小组和B小组协同开发。
其中有一个核心模块由A小组开发然后供B小组调用,这个核心模块被封装成了一个函数,这个函数就叫makeyoutiao()。
如果makeyoutiao()这个函数执行的很快并可以立即返回,那么B小组的同学只需要:调用makeyoutiao()等待该函数执行完成该函数执行完后继续后续流程
从程序执行的角度看这个过程是这样的:保存当前被执行函数的上下文开始执行makeyoutiao()这个函数makeyoutiao()执行完后,控制转回到调用函数中
1602596924819hr如果世界上所有的函数都像makeyoutiao()这么简单,那么程序员大概率就要失业了,还好程序的世界是复杂的,这样程序员才有了存在的价值。现实情况并不容易
现实中makeyoutiao()这个函数需要处理的数据非常庞大,假设有10000个,那么makeyoutiao(10000)不会立刻返回,而是可能需要10分钟才执行完成并返回。
这时你该怎么办呢?想一想这个问题。
可能有的同学就像把头埋在沙子里的鸵鸟一样:和刚才一样直接调用不可以吗,这样多简单。
是的,这样做没有问题,但就像爱因斯坦说的那样一切都应该尽可能简单,但是不能过于简单。
想一想直接调用会有什么问题?
显然直接调用的话,那么调用线程会被阻塞暂停,在等待10分钟后才能继续运行。在这10分钟内该线程不会被操作系统分配CPU,也就是说该线程得不到任何推进。
这并不是一种高效的做法。
没有一个程序员想死盯着屏幕10分钟后才能得到结果。
那么有没有一种更加高效的做法呢?
想一想我们上一篇中那个一直盯着你写代码的老板(见《从小白到高手,你需要理解同步与异步》),我们已经知道了这种一直等待直到另一个任务完成的模式叫做同步。
如果你是老板的话你会什么都不干一直盯着员工写代码吗?因此一种更好的做法是程序员在代码的时候老板该干啥干啥,程序员写完后自然会通知老板,这样老板和程序员都不需要相互等待,这种模式被称为异步。
回到我们的主题,这里一种更好的方式是调用makeyoutiao()这个函数后不再等待这个函数执行完成,而是直接返回继续后续流程,这样A小组的程序就可以和makeyoutiao()这个函数同时进行了,就像这样:
1602597258523hr在这种情况下,回调(callback)就必须出场了。为什么我们需要回调callback
有的同学可能还没有明白为什么在这种情况下需要回调,别着急,我们慢慢讲。
假设我们明日油条App代码第一版是这样写的:makeyoutiao(10000);sell();
可以看到这是最简单的写法,意思很简单,制作好油条后卖出去。
1602597572027hr我们已经知道了由于makeyoutiao(10000)这个函数10分钟才能返回,你不想一直死盯着屏幕10分钟等待结果,那么一种更好的方法是让makeyoutiao()这个函数知道制作完油条后该干什么,即,更好的调用makeyoutiao的方式是这样的:制作10000个油条,炸好后卖出去,因此调用makeyoutiao就变出这样了:makeyoutiao(10000,sell);
看到了吧,现在makeyoutiao这个函数多了一个参数,除了指定制作油条的数量外还可以指定制作好后该干什么,第二个被makeyoutiao这个函数调用的函数就叫回调,callback。
现在你应该看出来了吧,虽然sell函数是你定义的,但是这个函数却是被其它模块调用执行的,就像这样:
1602598090503hrmakeyoutiao这个函数是怎么实现的呢,很简单:voidmakeyoutiao(intnum,funccallback){制作油条callback();执行回调}
这样你就不用死盯着屏幕了,因为你把makeyoutiao这个函数执行完后该做的任务交代给makeyoutiao这个函数了,该函数制作完油条后知道该干些什么,这样就解放了你的程序。
有的同学可能还是有疑问,为什么编写makeyoutiao这个小组不直接定义sell函数然后调用呢?
不要忘了明日油条这个App是由A小组和B小组同时开发的,A小组在编写makeyoutiao时怎么知道B小组要怎么用这个模块,假设A小组真的自己定义sell函数就会这样写:voidmakeyoutiao(intnum){realmakeyoutiao(num);sell();执行回调}
同时A小组设计的模块非常好用,这时C小组也想用这个模块,然而C小组的需求是制作完油条后放到仓库而不是不是直接卖掉,要满足这一需求那么A小组该怎么写呢?voidmakeyoutiao(intnum){realmakeyoutiao(num);if(TeamB){sell();执行回调}elseif(TeamD){store();放到仓库}}
故事还没完,假设这时D小组又想使用呢,难道还要接着添加ifelse吗?这个问题该怎么解决呢?关于这个问题的答案,你可以参考这里。异步回调
故事到这里还没有结束。
在上面的示例中,虽然我们使用了回调这一概念,也就是调用方实现回调函数然后再将该函数当做参数传递给其它模块调用。
但是,这里依然有一个问题,那就是makeyoutiao函数的调用方式依然是同步的,关于同步异步请参考《从小白到高手,你需要理解同步与异步》,也就是说调用方是这样实现的:makeyoutiao(10000,sell);makeyoutiao函数返回前什么都做不了
1602598090503hr我们可以看到,调用方必须等待makeyoutiao函数返回后才可以继续后续流程,我们再来看下makeyoutiao函数的实现:voidmakeyoutiao(intnum,funccallback){realmakeyoutiao(num);callback();执行回调}
看到了吧,由于我们要制作10000个油条,makeyoutiao函数执行完需要10分钟,也就是说即便我们使用了回调,调用方完全不需要关心制作完油条后的后续流程,但是调用方依然会被阻塞10分钟,这就是同步调用的问题所在。
如果你真的理解了上一节的话应该能想到一种更好的方法了。
没错,那就是异步调用。
关于异步回调,你可以参考这里。新的编程思维模式
让我们再来仔细地看一下这个过程。
程序员最熟悉的思维模式是这样的:调用某个函数,获取结果处理获取到的结果resrequest();handle(res);
这就是函数的同步调用,只有request()函数返回拿到结果后,才能调用handle函数进行处理,request函数返回前我们必须等待,这就是同步调用,其控制流是这样的:
1602683285172hr但是如果我们想更加高效的话,那么就需要异步调用了,我们不去直接调用handle函数,而是作为参数传递给request:request(handle);
我们根本就不关心request什么时候真正的获取的结果,这是request该关心的事情,我们只需要把获取到结果后该怎么处理告诉request就可以了,因此request函数可以立刻返回,真的获取结果的处理可能是在另一个线程、进程、甚至另一台机器上完成。
这就是异步调用,其控制流是这样的:
1602684355493hr从编程思维上看,异步调用和同步有很大的差别,如果我们把处理流程当做一个任务来的话,那么同步下整个任务都是我们来实现的,但是异步情况下任务的处理流程被分为了两部分:第一部分是我们来处理的,也就是调用request之前的部分第二部分不是我们处理的,而是在其它线程、进程、甚至另一个机器上处理的。
我们可以看到由于任务被分成了两部分,第二部分的调用不在我们的掌控范围内,同时只有调用方才知道该做什么,因此在这种情况下回调函数就是一种必要的机制了。
也就是说回调函数的本质就是只有我们才知道做些什么,但是我们并不清楚什么时候去做这些,只有其它模块才知道,因此我们必须把我们知道的封装成回调函数告诉其它模块。
现在你应该能看出异步回调这种编程思维模式和同步的差异了吧。
接下来我们给回调一个较为学术的定义正式定义
在计算机科学中,回调函数是指一段以参数的形式传递给其它代码的可执行代码。
这就是回调函数的定义了。
回调函数就是一个函数,和其它函数没有任何区别。
注意,回调函数是一种软件设计上的概念,和某个编程语言没有关系,几乎所有的编程语言都能实现回调函数。
对于一般的函数来说,我们自己编写的函数会在自己的程序内部调用,也就是说函数的编写方是我们自己,调用方也是我们自己。
但回调函数不是这样的,虽然函数编写方是我们自己,但是函数调用方不是我们,而是我们引用的其它模块,也就是第三方库,我们调用第三方库中的函数,并把回调函数传递给第三方库,第三方库中的函数调用我们编写的回调函数,如图所示:
1601686717372hr而之所以需要给第三方库指定回调函数,是因为第三方库的编写者并不清楚在某些特定节点,比如我们举的例子油条制作完成、接收到网络数据、文件读取完成等之后该做什么,这些只有库的使用方才知道,因此第三方库的编写者无法针对具体的实现来写代码,而只能对外提供一个回调函数,库的使用方来实现该函数,第三方库在特定的节点调用该回调函数就可以了。
另一点值得注意的是,从图中我们可以看出回调函数和我们的主程序位于同一层中,我们只负责编写该回调函数,但并不是我们来调用的。
最后值得注意的一点就是回调函数被调用的时间节点,回调函数只在某些特定的节点被调用,就像上面说的油条制作完成、接收到网络数据、文件读取完成等,这些都是事件,也就是event,本质上我们编写的回调函数就是用来处理event的,因此从这个角度看回调函数不过就是eventhandler,因此回调函数天然适用于事件驱动编程eventdriven,我们将会在后续文章中再次回到这一主题。为什么异步回调这种思维模式正变得的越来越重要
在同步模式下,服务调用方会因服务执行而被阻塞暂停执行,这会导致整个线程被阻塞,因此这种编程方式天然不适用于高并发动辄几万几十万的并发连接场景,
针对高并发这一场景,异步其实是更加高效的,原因很简单,你不需要在原地等待,因此从而更好的利用机器资源,而回调函数又是异步下不可或缺的一种机制。回调地狱,callbackhell
有的同学可能认为有了异步回调这种机制应付起一切高并发场景就可以高枕无忧了。
实际上在计算机科学中还没有任何一种可以横扫一切包治百病的技术,现在没有,在可预见的将来也不会有,一切都是妥协的结果。
那么异步回调这种机制有什么问题呢?
实际上我们已经看到了,异步回调这种机制和程序员最熟悉的同步模式不一样,在可理解性上比不过同步,而如果业务逻辑相对复杂,比如我们处理某项任务时不止需要调用一项服务,而是几项甚至十几项,如果这些服务调用都采用异步回调的方式来处理的话,那么很有可能我们就陷入回调地狱中。
举个例子,假设处理某项任务我们需要调用四个服务,每一个服务都需要依赖上一个服务的结果,如果用同步方式来实现的话可能是这样的:aGetServiceA();bGetServiceB(a);cGetServiceC(b);dGetServiceD(c);
代码很清晰,很容易理解有没有。
我们知道异步回调的方式会更加高效,那么使用异步回调的方式来写将会是什么样的呢?GetServiceA(function(a){GetServiceB(a,function(b){GetServiceC(b,function(c){GetServiceD(c,function(d){。。。。});});});});
我想不需要再强调什么了吧,你觉得这两种写法哪个更容易理解,代码更容易维护呢?
博主有幸曾经维护过这种类型的代码,不得不说每次增加新功能的时候恨不得自己化为两个分身,一个不得不去重读一边代码;另一个在一旁骂自己为什么当初选择维护这个项目。
异步回调代码稍不留意就会跌到回调陷阱中,那么有没有一种更好的办法既能结合异步回调的高效又能结合同步编码的简单易读呢?
幸运的是,答案是肯定的,关于答案你可以参考这里。总结
在这篇文章中,我们从一个实际的例子出发详细讲解了回调函数这种机制的来龙去脉,这是应对高并发、高性能场景的一种极其重要的编码机制,异步加回调可以充分利用机器资源,实际上异步回调最本质上就是事件驱动编程,这是我们接下来要重点讲解的内容。
以家人之名插曲以家人之名结局《以家人之名》这部剧可以说是去年的热播剧,剧中的演员很受观众们的喜欢,而且里面的插曲也很好听,那么以家人之名插曲都有哪些呢?今天小编就来给你介绍一下。1以家人之名插曲……
红米Note11Pro量产,亿级像素120W256GB,依旧红米品牌开发七年之久,一直秉持初心为广大消费者带来极致性价比体验,也因为这份初心,红米手机在销量上非常可观,也拥有众多米粉;还在今年小米跃居全球第二名的好成绩出了自己的一份力量……
以家人之名贺子秋官配是谁以家人之名贺子秋结局是什么近段时间以来,随着电视剧《以家人之名》的热播,剧中的剧情越来越让大家摸不透了,剧中贺子秋的官配到底是谁呢?《以家人之名》贺子秋结局到底是什么?一起来看一下具体的情况。1以家人之……
以家人之名是小说改编吗以家人之名原著小说结局是什么近段时间以来,电视剧《以家人之名》正在热播当中,剧中的剧情很受大家的喜欢,《以家人之名》是小说改编的吗?《以家人之名》原著小说结局是什么呢?一起来看一下。1以家人之名是小说改编……
以家人之名贺子秋和齐明月在一起了吗贺子秋扮演者张新成资料介绍近段时间以来,电视剧《以家人之名》正在热播当中,剧中贺子秋的感情线颇受大家的关注,不少网友透露说最后贺子秋是和齐明月走到了一起,这是真的吗?贺子秋扮演者张新成资料介绍,一起来看……
中国北斗卫星来袭,美国GPS跌下落下帷幕?中国世界地位快速上如今人们的生活越来越智能化,很多高科技产品也逐渐充实着大家的日常生活。特别是在交通出行方面,可谓是发生了质的改变。如今开车导航,也不用再像以前那么麻烦,还得专门安装导航设备,购……
扬名立万海兆丰结局,扬名立万海兆丰谁演的扬名立万说出很多人不清楚,可扬名立万剧情中海兆丰产生什么样的结局,很多网友就想去了解一下有关的故事,看扬名立万提前铺设好海兆丰要发生的结局,不过后期呈现不多么完美,但有的网友就……
野马分鬃什么时候上映,野马分鬃演员表野马分鬃说出来很多人不清楚,可野马分鬃电影是什么时候上映,很多网友就想去了解一下有关的故事,看野马分鬃提早给相关的海报和预告片给公布,好让观众能熟悉大概剧情,但有的网友就不清楚……
微信和QQ,你比较喜欢用哪个?QQ,一般只在pc端用,沿用了以前的习惯,现在也多用于工作上的沟通。微信主要是朋友圈,手机上使用率非常高。但是现在越来越多的人醒来第一件事已经不是微信或qq了,而是打开头条,看……
为什么说红楼梦阴气重红楼梦里诡异的叹息声为什么说红楼梦阴气重:红楼梦里诡异的叹息声说起我国四大名著,最出名的就是《西游记》、《红楼梦》了,其中争议最多的就是红楼梦。前面我们知道了红楼梦细思恐极的细节,现在我们来……
汉王科技护航2022北京冬奥会随着冬奥会的进行,汉王科技的多个产品出现在北京冬奥会各赛区场馆、设施,为本次冬奥会护航,在张家口崇礼赛区,汉王科技热成像测温一体机和人证核验终端投入赛事保障,助力保障赛区通行人……
小幸福湖北播出喜迎新春姜武牛莉领衔群星贺岁冬去春来,福到万家。新春佳节将至,让人对ldquo;家rdquo;和ldquo;幸福rdquo;有了更多的眷恋和感怀。挣脱钢筋水泥丛林的规约与刻板,感受平凡生活里的家长里短、人……
港股收评恒指涨0。72阿里巴巴涨近6商汤科技涨超141月6日早盘,恒指低开低走,午后尾盘,恒指暴力拉升。截至收盘,恒指涨0。72,报23072。86点,国企指数涨0。66,红筹指数跌0。12。恒生科技指数尾盘暴力反弹涨1。……
很贵但是很值的数码产品,谁不说一句真香越来越多的数码产品成为了当代年轻人的必备工具,比如智能手机就是大多数人生活完全离不开的产品,不不过不知道大家发现没有,智能手机市场的发展越来越迅速,同时智能手机的价格上限也越来……
为什么中国只发现了一个陨石坑?目前全球得到确认的陨石坑近200个,可中国偌大的地图上只有孤零零的一个独苗辽宁岫岩陨石坑,而且还是2010年才刚刚获得身份证的。中国领土占到了地球陆地总面积的115,为何仅仅拥……
手机充电一夜不拔,对电池有坏处吗?为了节省时间先说结论:手机充电一夜不拔,对电池有那么一丢丢小到可以忽略不计的影响,但还没到需要半夜起来拔充电器的地步。只要不是给手机用到了低电量(20以下)状态,更……
颜值高自拍强,适合小姐姐的3款手机,2000元预算就能搞定市面上的手机机型很多,不同的配置有不同的价格,旗舰机的价格普遍比较高。但是预算低也不用担心,同样可以入手性能强劲的主力机。如果预算在2000元左右,那么可以看看以下3款手机,不……
分析师iPhone13高端机型将提供1TB内存近日,Wedbush分析师DanielIves在一份投资者报告中表示,苹果公司将于9月第三周发布其系列,高端Pro机型将提供1TB存储版本。相比之下,1TB容量将是iPhone……
荣耀脱钩华为,子品牌市场重新洗牌,赢家会是谁?5000价位没缺点,从iQOO8吹响的信号来看,目前堆料、性价比还是成为了子品牌最善于抢夺市场的手段之一。身为iQOO的年度旗舰,iQOO8系列凭借狂暴的堆料和大幅领先E……
所有人3月新规来了!这些变化你要知道!新华网北京2月27日电(记者卢俊宇)3月起,将有一批新的法律法规正式实施,影响我们的生活。全国性新规支付宝信用卡还款超过2000元将收取0。1服务费信用卡免费……
全时谢幕邻家关停便利蜂裁员这届便利店创业者不行来源:投中网作者:王满华便利店在未来有很大的市场需求,将会成为未来生活的社区枢纽,但是这届便利店不行。去年年底,公司门口的全时有一天开始突然断货了,冰柜都是空的,饮……
汽车备胎安全使用常识快来学习一下凡事都会有备用计划,开车也一样,大部分汽车都配有备用轮胎,当遇到紧急情况时,我们可以换上它,以便不时之需。用着用着,有些车主就不会将备胎换下来,更换正常的轮胎,有的可能是……
LDS天线技术及应用领域详解众所周知,智能手机大屏化后,需要加载镁铝合金支撑,防止手机软骨病,金属件的加载,使手机中射频接收环境变差,天线被逼迫到外壳上是大势所趋,而LDS天线可以大幅度减小天线尺寸,成为……
有数以百万计的工作岗位但工人短缺为什么这令人担忧新冠肺炎疫情大流行不仅对全球公共卫生造成巨大影响,也给经济带来混乱,供应链中断和劳动力短缺是全球企业面临的一大问题。劳动力市场发生的变化变得越来越明显,随着经济重新开放,……