我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么理解回调函数

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么理解回调函数

这篇文章主要介绍“怎么理解回调函数”,在日常操作中,相信很多人在怎么理解回调函数问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解回调函数”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一切要从这样的需求说起

假设你们公司要开发下一代国民App“明日油条”,一款主打解决国民早餐问题的App,为了加快开发进度,这款应用由A小组和B小组协同开发。

其中有一个核心模块由A小组开发然后供B小组调用,这个核心模块被封装成了一个函数,这个函数就叫make_youtiao()。

如果make_youtiao()这个函数执行的很快并可以立即返回,那么B小组的同学只需要:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 调用make_youtiao()

  3. 等待该函数执行完成

  4. 该函数执行完后继续后续流程

从程序执行的角度看这个过程是这样的:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 保存当前被执行函数的上下文

  3. 开始执行make_youtiao()这个函数

  4. make_youtiao()执行完后,控制转回到调用函数中

怎么理解回调函数

如果世界上所有的函数都像make_youtiao()这么简单,那么程序员大概率就要失业了,还好程序的世界是复杂的,这样程序员才有了存在的价值。

现实并不容易

现实中make_youtiao()这个函数需要处理的数据非常庞大,假设有10000个,那么make_youtiao(10000)不会立刻返回,而是可能需要10分钟才执行完成并返回。

这时你该怎么办呢?想一想这个问题。

可能有的同学会问,和刚才一样直接调用不可以吗,这样多简单。

是的,这样做没有问题,但就像爱因斯坦说的那样“一切都应该尽可能简单,但是不能过于简单”。

想一想直接调用会有什么问题?

显然直接调用的话,那么调用线程会被阻塞暂停,在等待10分钟后才能继续运行。在这10分钟内该线程不会被操作系统分配CPU,也就是说该线程得不到任何推进。

这并不是一种高效的做法。

没有一个程序员想死盯着屏幕10分钟后才能得到结果。

那么有没有一种更加高效的做法呢?

想一想我们上一篇中那个一直盯着你写代码的老板(见《从小白到高手,你需要理解同步与异步》),我们已经知道了这种一直等待直到另一个任务完成的模式叫做同步。

如果你是老板的话你会什么都不干一直盯着员工写代码吗?因此一种更好的做法是程序员在代码的时候老板该干啥干啥,程序员写完后自然会通知老板,这样老板和程序员都不需要相互等待,这种模式被称为异步。

回到我们的主题,这里一种更好的方式是调用make_youtiao()这个函数后不再等待这个函数执行完成,而是直接返回继续后续流程,这样A小组的程序就可以和make_youtiao()这个函数同时进行了,就像这样:

怎么理解回调函数

在这种情况下,回调(callback)就必须出场了。

为什么我们需要回调callback

有的同学可能还没有明白为什么在这种情况下需要回调,别着急,我们慢慢讲。

假设我们“明日油条”App代码第一版是这样写的:

make_youtiao(10000); sell();

可以看到这是最简单的写法,意思很简单,制作好油条后卖出去。

怎么理解回调函数

我们已经知道了由于make_youtiao(10000)这个函数10分钟才能返回,你不想一直死盯着屏幕10分钟等待结果,那么一种更好的方法是让make_youtiao()这个函数知道制作完油条后该干什么,即,更好的调用make_youtiao的方式是这样的:“制作10000个油条,炸好后卖出去”,因此调用make_youtiao就变出这样了:

make_youtiao(10000, sell);

看到了吧,现在make_youtiao这个函数多了一个参数,除了指定制作油条的数量外还可以指定制作好后该干什么,第二个被make_youtiao这个函数调用的函数就叫回调,callback。

现在你应该看出来了吧,虽然sell函数是你定义的,但是这个函数却是被其它模块调用执行的,就像这样:

怎么理解回调函数

make_youtiao这个函数是怎么实现的呢,很简单:

void make_youtiao(int num, func call_back) {     // 制作油条     call_back(); //执行回调  }

这样你就不用死盯着屏幕了,因为你把make_youtiao这个函数执行完后该做的任务交代给make_youtiao这个函数了,该函数制作完油条后知道该干些什么,这样就解放了你的程序。

有的同学可能还是有疑问,为什么编写make_youtiao这个小组不直接定义sell函数然后调用呢?

不要忘了明日油条这个App是由A小组和B小组同时开发的,A小组在编写make_youtiao时怎么知道B小组要怎么用这个模块,假设A小组真的自己定义sell函数就会这样写:

void make_youtiao(int num) {     real_make_youtiao(num);     sell(); //执行回调  }

同时A小组设计的模块非常好用,这时C小组也想用这个模块,然而C小组的需求是制作完油条后放到仓库而不是不是直接卖掉,要满足这一需求那么A小组该怎么写呢?

void make_youtiao(int num) {     real_make_youtiao(num);          if (Team_B) {        sell(); // 执行回调     } else if (Team_D) {        store(); // 放到仓库     } }

故事还没完,假设这时D小组又想使用呢,难道还要接着添加if  else吗?这样的话A小组的同学只需要维护make_youtiao这个函数就能做到工作量饱满了,显然这是一种非常糟糕的设计。

所以你会看到,制作完油条后接下来该做什么不是实现make_youtiao的A小组该关心的事情,很明显只有调用make_youtiao这个函数的使用方才知道。

因此make_youtiao的A小组完全可以通过回调函数将接下来该干什么交给调用方实现,A小组的同学只需要针对回调函数这一抽象概念进行编程就好了,这样调用方在制作完油条后不管是卖掉、放到库存还是自己吃掉等等想做什么都可以,A小组的make_youtiao函数根本不用做任何改动,因为A小组是针对回调函数这一抽象概念来编程的。

以上就是回调函数的作用,当然这也是针对抽象而不是具体实现进行编程这一思想的威力所在。面向对象中的多态本质上就是让你用来针对抽象而不是针对实现来编程的。

异步回调

故事到这里还没有结束。

在上面的示例中,虽然我们使用了回调这一概念,也就是调用方实现回调函数然后再将该函数当做参数传递给其它模块调用。

但是,这里依然有一个问题,那就是make_youtiao函数的调用方式依然是同步的,关于同步异步请,也就是说调用方是这样实现的:

make_youtiao(10000, sell); // make_youtiao函数返回前什么都做不了

 怎么理解回调函数

我们可以看到,调用方必须等待make_youtiao函数返回后才可以继续后续流程,我们再来看下make_youtiao函数的实现:

void make_youtiao(int num, func call_back) {     real_make_youtiao(num);     call_back(); //执行回调  }

看到了吧,由于我们要制作10000个油条,make_youtiao函数执行完需要10分钟,也就是说即便我们使用了回调,调用方完全不需要关心制作完油条后的后续流程,但是调用方依然会被阻塞10分钟,这就是同步调用的问题所在。

如果你真的理解了上一节的话应该能想到一种更好的方法了。

没错,那就是异步调用。

反正制作完油条后的后续流程并不是调用方该关心的,也就是说调用方并不关心make_youtiao这一函数的返回值,那么一种更好的方式是:把制作油条的这一任务放到另一个线程(进程)、甚至另一台机器上。

如果用线程实现的话,那么make_youtiao就是这样实现了:

void make_youtiao(int num, func call_back) {     // 在新的线程中执行处理逻辑     create_thread(real_make_youtiao,                   num,                   call_back); }

 怎么理解回调函数

看到了吧,这时当我们调用make_youtiao时就会立刻返回,即使油条还没有真正开始制作,而调用方也完全无需等待制作油条的过程,可以立刻执行后流程:

make_youtiao(10000, sell); // 立刻返回 // 执行后续流程

这时调用方的后续流程可以和制作油条同时进行,这就是函数的异步调用,当然这也是异步的高效之处。

新的编程思维模式

让我们再来仔细的看一下这个过程。

程序员最熟悉的思维模式是这样的:

  • 调用某个函数,获取结果

  • 处理获取到的结果

res = request(); handle(res);

这就是函数的同步调用,只有request()函数返回拿到结果后,才能调用handle函数进行处理,request函数返回前我们必须等待,这就是同步调用,其控制流是这样的:

怎么理解回调函数

但是如果我们想更加高效的话,那么就需要异步调用了,我们不去直接调用handle函数,而是作为参数传递给request:

request(handle);

我们根本就不关心request什么时候真正的获取的结果,这是request该关心的事情,我们只需要把获取到结果后该怎么处理告诉request就可以了,因此request函数可以立刻返回,真的获取结果的处理可能是在另一个线程、进程、甚至另一台机器上完成。

这就是异步调用,其控制流是这样的:

怎么理解回调函数

从编程思维上看,异步调用和同步有很大的差别,如果我们把处理流程当做一个任务来的话,那么同步下整个任务都是我们来实现的,但是异步情况下任务的处理流程被分为了两部分:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 第一部分是我们来处理的,也就是调用request之前的部分

  3. 第二部分不是我们处理的,而是在其它线程、进程、甚至另一个机器上处理的。

我们可以看到由于任务被分成了两部分,第二部分的调用不在我们的掌控范围内,同时只有调用方才知道该做什么,因此在这种情况下回调函数就是一种必要的机制了。

也就是说回调函数的本质就是“只有我们才知道做些什么,但是我们并不清楚什么时候去做这些,只有其它模块才知道,因此我们必须把我们知道的封装成回调函数告诉其它模块”。

现在你应该能看出异步回调这种编程思维模式和同步的差异了吧。

接下来我们给回调一个较为学术的定义

正式定义

在计算机科学中,回调函数是指一段以参数的形式传递给其它代码的可执行代码。

这就是回调函数的定义了。

回调函数就是一个函数,和其它函数没有任何区别。

注意,回调函数是一种软件设计上的概念,和某个编程语言没有关系,几乎所有的编程语言都能实现回调函数。

对于一般的函数来说,我们自己编写的函数会在自己的程序内部调用,也就是说函数的编写方是我们自己,调用方也是我们自己。

但回调函数不是这样的,虽然函数编写方是我们自己,但是函数调用方不是我们,而是我们引用的其它模块,也就是第三方库,我们调用第三方库中的函数,并把回调函数传递给第三方库,第三方库中的函数调用我们编写的回调函数,如图所示:

怎么理解回调函数

而之所以需要给第三方库指定回调函数,是因为第三方库的编写者并不清楚在某些特定节点,比如我们举的例子油条制作完成、接收到网络数据、文件读取完成等之后该做什么,这些只有库的使用方才知道,因此第三方库的编写者无法针对具体的实现来写代码,而只能对外提供一个回调函数,库的使用方来实现该函数,第三方库在特定的节点调用该回调函数就可以了。

另一点值得注意的是,从图中我们可以看出回调函数和我们的主程序位于同一层中,我们只负责编写该回调函数,但并不是我们来调用的。

最后值得注意的一点就是回调函数被调用的时间节点,回调函数只在某些特定的节点被调用,就像上面说的油条制作完成、接收到网络数据、文件读取完成等,这些都是事件,也就是event,本质上我们编写的回调函数就是用来处理event的,因此从这个角度看回调函数不过就是event  handler,因此回调函数天然适用于事件驱动编程event-driven,我们将会在后续文章中再次回到这一主题。

回调的类型

我们已经知道有两种类型的回调,这两种类型的回调区别在于回调函数被调用的时机。

注意,接下来会用到同步和异步的概念,对这两个概念不熟悉的同学可以参考上一盘文章《从小白到高手,你需要理解同步和异步》。

同步回调

这种回调就是通常所说的同步回调synchronous callbacks、也有的将其称为阻塞式回调blocking  callbacks,或者什么修饰都没有,就是回调,callback,这是我们最为熟悉的回调方式。

当我们调用某个函数A并以参数的形式传入回调函数后,在A返回之前回调函数会被执行,也就是说我们的主程序会等待回调函数执行完成,这就是所谓的同步回调。

怎么理解回调函数

有同步回调就有异步回调。

异步回调

不同于同步回调,  当我们调用某个函数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) {  ....  });  });  });

我想不需要再强调什么了吧,你觉得这两种写法哪个更容易理解,代码更容易维护呢?博主有幸曾经维护过这种类型的代码,不得不说每次增加新功能的时候恨不得自己化为两个分身,一个不得不去重读一边代码;另一个在一旁骂自己为什么当初选择维护这个项目。异步回调代码稍不留意就会跌到回调陷阱中,那么有没有一种更好的办法既能结合异步回调的高效又能结合同步编码的简单易读呢?幸运的是,答案是肯定的,我们会在后续文章中详细讲解这一技术。

到此,关于“怎么理解回调函数”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎么理解回调函数

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

深入理解C#中回调函数

回调函数在C#中广泛用于解耦代码和实现异步操作。回调函数是一种在某个事件触发后执行的代码块,它作为委托类型的方法被传递给其他方法或类。通过使用回调函数,可以实现代码的可读性和可维护性,以及在操作完成时执行代码而无需阻塞调用线程。它广泛应用于事件处理,如按钮点击和网络请求。理解回调函数的概念和用法对于充分利用C#的异步编程功能至关重要。
深入理解C#中回调函数
2024-04-02

c++异步回调函数怎么调用

在C++中,异步回调函数通常使用函数指针或者函数对象来表示。下面是调用异步回调函数的几种常见方法:1. 使用函数指针:cppvoid callbackFunction(int result) {// 处理回调函数的逻辑}void async
2023-10-20

getjson回调函数不执行怎么解决

如果您的getJSON回调函数没有执行,有几种可能的解决方案:检查URL是否正确:确保您传递给getJSON函数的URL是正确的,并且能够返回有效的JSON数据。检查网络连接:确保您的网络连接正常,可以访问所请求的URL。检查回调函数是否正
getjson回调函数不执行怎么解决
2024-03-02

python回调函数怎么传参数

在 Python 中,可以使用 functools.partial() 函数来传递参数给回调函数。functools.partial() 函数允许您创建一个新的可调用对象,其中的一些参数被预先填充。您可以使用它来创建一个带有预设参数的新函数
2023-10-19

jquery回调函数怎么使用

本篇内容介绍了“jquery回调函数怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在jquery中,回调函数就是一个被作为参数传递的
2023-07-04

python怎么获取回调函数返回值

在Python中,可以使用callback函数来获取回调函数的返回值。以下是一个示例:def callback_function(data):# 执行一些操作result = data + 10return resultdef main_
python怎么获取回调函数返回值
2024-02-29

Node.js中的回调函数怎么用

这篇文章主要介绍了Node.js中的回调函数怎么用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Node.js中的回调函数怎么用文章都会有所收获,下面我们一起来看看吧。同样,在Node.js中,比如I/O操作发
2023-06-27

C++中怎么实现回调函数

今天就跟大家聊聊有关C++中怎么实现回调函数,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。C++回调函数代码示例:#include < string> #include < iost
2023-06-17

PHP 函数如何返回回调函数?

php 函数可以通过使用以下语法返回回调函数:定义函数以返回回调函数:function returncallback(): callable回调函数可以接受任何参数并返回任何类型的值确保回调函数是可调用的(函数、闭包或可调用的对象)回调函数
PHP 函数如何返回回调函数?
2024-04-10

Node.js 回调函数实例详解

Node.js 回调函数 阻塞与非阻塞 node.js 异步编程的直接体现就是回调。 异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。 回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都
2022-06-04

C 语言回调函数详解

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。1. 什么是回调函数?
2023-06-03

C#怎么调用C++动态库接口函数和回调函数

本文小编为大家详细介绍“C#怎么调用C++动态库接口函数和回调函数”,内容详细,步骤清晰,细节处理妥当,希望这篇“C#怎么调用C++动态库接口函数和回调函数”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1. 前言
2023-06-29

Python中的回调函数怎么使用

本文小编为大家详细介绍“Python中的回调函数怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python中的回调函数怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。摘要主要是介绍python 的
2023-07-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录