什么是回调函数?
什么是回调函数?
由于该死的名称,开发人员常常对回调函数感到困惑。
回调函数是指一个函数:
想象一下回调函数的工作原理,它就像是一个被传递给其他函数的函数,在这个函数之后"被调用"。
也许更好的名称应该是"在之后调用"的函数。
当我们想要在前一个事件完成时执行某个活动时,这种结构非常有用,特别是在异步行为中。
伪代码:
// A function which accepts another function as an argument
// (and will automatically invoke that function when it completes - note that there is no explicit call to callbackFunction)
funct printANumber(int number, funct callbackFunction) {
printout("The number you provided is: " + number);
}
// a function which we will use in a driver function as a callback function
funct printFinishMessage() {
printout("I have finished printing numbers.");
}
// Driver method
funct event() {
printANumber(6, printFinishMessage);
}
如果调用event(),将会得到什么结果:
The number you provided is: 6
I have finished printing numbers.
这里的输出顺序很重要。由于回调函数是在之后调用的,所以"I have finished printing numbers"会最后打印出来,而不是第一个。parent(cb) {dostuff1(); cb(); dostuff2()}
,那么这个函数不被视为“回调”函数吗? - Max Yari回调函数是你提供给另一段代码的函数,允许它被该代码调用。
为什么想要这样做?假设有一个需要调用的服务。如果服务立即返回,您只需执行以下操作:
例如,假设该服务是阶乘函数。当您需要得到 5!
的值时,您将调用 factorial(5)
,以下步骤将发生:
当前执行位置被保存(在栈上,但这不重要)
执行被交给 factorial
当 factorial
完成后,它将结果放在某个可以访问到的地方
执行回到 [1] 中的位置
现在假设由于你传递给 factorial
的数字很大,导致阶乘函数非常耗时,比如需要在某个超级计算机集群上运行 5 分钟才能返回结果。那么,您可以:
保持当前设计,把程序在你睡觉时运行,这样你不用一半时间盯着屏幕
让程序在 factorial
运行的同时做其他事情
如果您选择第二个选项,则回调函数可能适合您。
为了利用回调模式,您希望以以下方式调用 factorial
:
factorial(really_big_number, what_to_do_with_the_result)
第二个参数what_to_do_with_the_result
是一个函数,您可以将其发送到factorial
中,希望在返回结果之前factorial
会调用该函数。
是的,这意味着factorial
需要支持回调。
现在假设您想要将参数传递给回调函数。但由于您不会调用它,而是factorial
会调用,因此factorial
需要被编写以允许您传入参数,并在调用回调时将它们直接传递给它。它可能看起来像这样:factorial (number, callback, params)
{
result = number! // i can make up operators in my pseudocode
callback (result, params)
}
现在 factorial 允许此模式,您的回调函数可能如下所示:
<code>logIt (number, logger)
{
logger.log(number)
}
</code>
您调用
factorial
的方式如下:
<code>factorial(42, logIt, logger)
</code>
如果你希望从
logIt
中返回一些内容怎么办?很抱歉,你做不到,因为 factorial
不会注意它。
那么,为什么 factorial
不能返回回调函数的返回值呢?
使其非阻塞
由于执行应该在 factorial
完成后移交给回调函数,因此它实际上不应该向其调用者返回任何内容。最理想的情况是,它会在另一个线程/进程/机器中启动工作并立即返回,以便您可以继续进行,可能像这样:
factorial(param_1, param_2, ...)
{
new factorial_worker_task(param_1, param_2, ...);
return;
}
现在这是一个“异步调用”,意味着当您调用它时,它会立即返回,但实际上它还没有完成其工作。因此,您需要机制来检查它,并在完成时获取其结果,这使得您的程序更加复杂。
顺便说一下,使用这种模式,factorial_worker_task
可以异步启动回调并立即返回。
那么你该怎么办呢?
答案是要保持在回调模式中。每当您想要编写
a = f()
g(a)
如果要异步调用f
函数,你应该编写:
f(g)
其中 g
作为回调函数被传递。
这将从根本上改变程序的流拓扑结构,需要一些时间来适应。
你的编程语言可以通过提供一种创建临时函数的方式来帮助你。在上面的代码中,函数 g
可能只是一个小小的函数,比如 print (2*a+1)
。如果你的语言要求你定义这个函数,并给它一个完全不必要的名字和参数,那么如果你经常使用这种模式,你的生活将会变得不愉快。
如果你的语言允许你创建 lambda 表达式,那么你就会更好地解决问题。你将编写类似于以下内容的代码:
f( func(a) { print(2*a+1); })
如何传递回调函数
你应该如何将回调函数传递给factorial
呢?其实,你可以采用多种方式:
如果被调用的函数在同一个进程中运行,可以传递一个函数指针。
或者,你想在程序中维护一个字典,记录每个函数的名称和指针。
也可能你的语言支持在函数内部定义另一个函数,比如lambda表达式!语言内部会创建某种对象并传递指针,但你不用担心这些细节。
或许你正在调用的函数是在完全不同的机器上运行的,而你是通过类似HTTP的网络协议来调用它的。这时候你可以将你的回调函数公开为可通过HTTP调用的函数,并传递其URL。
你可以根据具体情况选择使用哪种方法。
回调函数的最近兴起
在我们进入Web时代后,我们调用的服务通常都在网络上。我们经常无法控制那些服务,即我们没有编写它们,也不能保证它们的正常运行或性能表现。
但我们不能期望我们的程序在等待这些服务响应时阻塞。因此,服务提供商通常使用回调模式设计API。
JavaScript非常好地支持回调函数,例如lambda表达式和闭包。在前端和后端,JavaScript世界中有很多活动,甚至正在开发适用于移动设备的JavaScript平台。
随着我们不断前进,越来越多的人会编写异步代码,而理解这种方式将变得至关重要。
维基百科上的回调页面对此解释得非常清楚:
在计算机编程中,回调是指传递作为参数给其他代码的可执行代码或可执行代码片段的引用。这允许低层软件层调用在高层定义的子程序(或函数)。
每天,我去上班。老板告诉我:
哦,当你完成那个任务后,我还有一个额外的任务给你:
太好了。他递给我一张纸条上面写着一个任务 - 这个任务是一个回调函数。它可以是任何东西:
ben.doWork(完成后洗我的车)
明天可能是:
ben.doWork(完成后告诉我我有多棒)
关键点在于回调必须在我完成工作之后进行!你最好阅读上面其他答案中包含的代码,我就不再重复了。
简单来说,这是一个由用户或浏览器在某些事件发生或处理完某些代码后调用而非由您自己调用的函数。
回调函数是一种在特定条件满足时应该调用的函数。与立即被调用不同,回调函数在将来的某个时间点被调用。
通常在启动异步任务(即在调用函数返回后某个时间点完成)时使用。
例如,一个请求网页的函数可能要求其调用者提供回调函数,当网页下载完毕时将被调用。
回调函数最易于通过电话系统来描述。函数调用类似于打电话给某人,问一个问题,得到答案,然后挂断电话;添加回调函数可以改变这个比喻,使你在问完问题后还要把你的姓名和电话号码告诉她,这样她就可以回电话给你并告诉你答案。
-- Paul Jakubik,《C++中的回调实现》
我认为“回调函数”这个术语被错误地在很多地方使用。我的定义类似于:
回调函数是一种你将其传递给其他人并允许他们在某个时间点调用它的函数。
我认为人们只读了维基百科定义中的第一句话:
回调是对可执行代码的引用,或者是作为参数传递给其他代码的可执行代码片段。
我一直在使用许多API,看到了许多糟糕的例子。许多人倾向于将函数指针(对可执行代码的引用)或匿名函数(可执行代码片段)命名为“回调”,如果它们只是函数,为什么需要另一个名称呢?
实际上,只有维基百科定义中的第二句话揭示了回调函数和常规函数之间的区别:
这允许较低级别的软件层调用高级别层定义的子例程(或函数)。
因此,区别在于您要将函数传递给谁以及如何调用您传入的函数。如果您只定义一个函数并将其传递给另一个函数,并在该函数体中直接调用它,请不要称其为回调。定义说您传入的函数将由“较低级别”函数调用。
我希望人们能停止在模棱两可的情况下使用这个词,这只会使人们更加困惑而不是更好地理解。
回调函数 vs 回调
回调是指在另一个函数执行完毕后要执行的函数——因此得名“回调”。
什么是回调函数?
otherFunction
)并在otherFunction
内部调用(或执行)的函数。 function action(x, y, callback) {
return callback(x, y);
}
function multiplication(x, y) {
return x * y;
}
function addition(x, y) {
return x + y;
}
alert(action(10, 10, multiplication)); // output: 100
alert(action(10, 10, addition)); // output: 20
在SOA中,回调允许插件模块从容器/环境中访问服务。
这使得回调听起来像是在方法末尾的返回语句。
我不确定它们就是这样。
我认为回调实际上是对函数的调用,作为另一个函数被调用并完成的结果。
我也认为回调旨在处理原始调用,在某种意义上是“嘿!你要求的那件事?我已经完成了 - 只是想让你知道 - 回到你这里了。”