什么是回调函数?

时间:2009-05-05 10:18:10

标签: language-agnostic callback

什么是回调函数?

21 个答案:

答案 0 :(得分:614)

由于该死的事物的名称,开发人员经常对回调感到困惑。

回调函数是一个函数:

  • 可以通过其他功能访问,
  • 如果第一个函数完成,则在第一个函数之后调用

一种想象回调函数如何工作的好方法是它是一个函数,它被传递给函数的“在后面调用”。

在“功能之后,可能更好的名称是”。

此构造对异步行为非常有用,我们希望在上一个事件完成时发生活动。

<强>伪代码:

// 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.

此处输出的顺序很重要。由于之后调用了回调函数,因此最后打印“我已完成打印数字”,而不是先打印。

由于使用了指针语言,回调被称为所谓的回调。如果您不使用其中一个,请不要使用名称“回调”。只是理解它只是一个名称来描述一个方法,它作为另一个方法的参数提供,这样当调用父方法时(无论条件,如按钮点击,计时器滴答等)及其方法体完成,然后调用回调函数。

某些语言支持支持多个回调函数参数的构造,并根据父函数的完成方式调用(即在父函数成功完成的情况下调用一个回调,在父函数完成的情况下调用另一个回调)函数抛出特定错误等。)

答案 1 :(得分:205)

不透明定义

回调函数是您提供给另一段代码的函数,允许该代码调用它。

Contrived example

你为什么要这样做?让我们说你需要调用一个服务。如果服务立即返回,您只需:

  1. 称之为
  2. 等待结果
  3. 结果进入后继续
  4. 例如,假设该服务是factorial函数。如果您想要5!的值,则可以调用factorial(5),并执行以下步骤:

    1. 您当前的执行地点已保存(在堆叠中,但这并不重要)

    2. 执行被移交给factorial

    3. factorial完成时,它会将结果放在您可以访问的地方

    4. 执行回到[1]

    5. 中的位置

      现在假设factorial花了很长时间,因为你给它提供了大量数据,它需要在一些超级计算群集上运行。我们假设您需要5分钟才能返回结果。你可以:

      1. 保持您的设计并在晚上睡觉时运行您的程序,这样您就不会在一半时间盯着屏幕

      2. 设计您的程序,以便在factorial正在做其事时做其他事情

      3. 如果您选择第二个选项,那么回调可能适合您。

        端到端设计

        为了利用回调模式,您希望能够以下列方式调用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允许此模式,您的回调可能如下所示:

        logIt (number, logger)
        {
            logger.log(number)
        }
        

        您对factorial的致电

        factorial(42, logIt, logger)
        

        如果您想从logIt返回某些内容,该怎么办?好吧,你不能,因为factorial并没有注意它。

        那么,为什么factorial只能返回你的回调返回的内容?

        使其成为非阻塞

        由于执行意味着在factorial完成时被移交给回调,所以它实际上不应该向其调用者返回任何内容。理想情况下,它会以某种方式在另一个线程/进程/机器中启动其工作并立即返回,以便您可以继续,可能是这样的:

        factorial(param_1, param_2, ...)
        {
            new factorial_worker_task(param_1, param_2, ...);
            return;
        }
        

        现在这是一个&#34;异步调用&#34;,这意味着当你调用它时,它会立即返回,但还没有真正完成它的工作。因此,您确实需要机制来检查它,并在完成后获得结果,并且您的程序在此过程中变得更加复杂。

        顺便说一句,使用这种模式,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?好吧,你可以通过多种方式实现这一目标。

        1. 如果被调用的函数在同一进程中运行,则可以传递一个函数指针

        2. 或者您可能希望在程序中维护fn name --> fn ptr的字典,在这种情况下,您可以传递名称

        3. 也许您的语言允许您就地定义函数,可以作为lambda!在内部,它正在创建某种对象并传递指针,但您不必担心这一点。

        4. 也许你正在调用的函数是在一台完全独立的机器上运行的,而你正在使用像HTTP这样的网络协议来调用它。您可以将回调公开为HTTP可调用函数,并传递其URL。

        5. 你明白了。

          最近回调的上升

          在我们进入的这个网络时代,我们调用的服务通常是通过网络进行的。我们通常对这些服务没有任何控制权,即我们没有对它们进行编写,我们不会对它们进行维护,我们无法确保它们处于启动状态或者它们如何执行

          但我们不能指望我们的程序会在我们等待这些服务作出响应时阻止。意识到这一点,服务提供商通常使用回调模式设计API。

          JavaScript非常好地支持回调,例如与lambdas和闭合。 JavaScript世界中有很多活动,无论是在浏览器上还是在服务器上。甚至还有针对移动设备开发的JavaScript平台。

          随着我们向前发展,越来越多的人将编写异步代码,这种理解将是必不可少的。

答案 2 :(得分:90)

请注意,回调只有一个字。

The wikipedia callback page解释得非常好。

来自维基百科页面的

引用:

  

在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用更高级别层中定义的子例程(或函数)。

答案 3 :(得分:42)

外行响应将是一个函数不是由您调用,而是由某个事件发生后或某些代码处理后由用户或浏览器调用。

答案 4 :(得分:40)

回调函数是在满足某个条件时应该调用的函数。不是立即调用,而是在将来某个时刻调用回调函数。

通常在启动任务时使用它将异步完成(即在调用函数返回后将完成一段时间)。

例如,请求网页的功能可能要求其调用者提供将在网页下载完成后调用的回调函数。

答案 5 :(得分:33)

我认为这种“回调”术语在很多地方都被错误地使用过。我的定义是这样的:

  

回调函数是一个传递给某人并让它的函数   他们在某个时间点叫它。

我认为人们只是阅读维基定义的第一句话:

  

回调是对可执行代码或一段代码的引用   可执行代码,作为参数传递给其他代码。

我一直在使用大量的API,看到各种不好的例子。许多人倾向于命名一个函数指针(对可执行代码的引用)或匿名函数(一段可执行代码)“回调”,如果它们只是函数,为什么你需要另一个名字?

实际上只有wiki定义中的第二句显示了回调函数和普通函数之间的差异:

  

这允许较低级别的软件层调用子例程(或   功能)在更高级别的层中定义。

所以不同之处在于你要传递函数的人以及传入函数的调用方式。如果您只是定义一个函数并将其传递给另一个函数并直接在该函数体中调用它,请不要将其称为回调函数。定义说你传入的函数将被“低级”函数调用。

我希望人们可以在模棱两可的语境中停止使用这个词,它不能帮助人们更好地理解。

答案 6 :(得分:30)

回调最容易用电话系统来描述。功能调用类似于通过电话呼叫某人,向她询问问题,获得答案以及挂断电话;添加回调更改了类比,以便在向她询问问题后,您还会告诉她您的姓名和号码,以便她可以给您回复答案。

- Paul Jakubik,“C ++中的回调实现”

答案 7 :(得分:28)

让我们保持简单。什么是回叫功能?

通过寓言和类比示例

我有一个秘书。我每天都要求她:(i)在邮局放下公司的外发邮件,然后 她完成了这项工作:(ii)我在其中一个sticky notes上为她写的任何任务。

现在,粘滞便笺上的任务是什么?这项任务每天都在变化。

假设在这一天,我要她打印一些文件。所以我把它写在便利贴上,然后把它和她需要发布的外发邮件一起放在她的桌子上。

总结:

  1. 首先,她需要放下邮件和
  2. 完成之后,她需要打印一些文件。
  3. 回叫功能是第二个任务:打印掉那些文件。因为它是在邮件被丢弃之后完成的,也是因为告诉她打印文件的粘滞便笺与她需要发布的邮件一起发给她。

    现在让我们将其与编程词汇联系起来

    • 这种情况下的方法名称是:DropOffMail。
    • 回调函数是:PrintOffDocuments。 PrintOffDocuments是回调函数,因为我们希望秘书只在DropOffMail运行之后才这样做。
    • 所以我会&#34;传递:PrintOffDocuments作为&#34;参数&#34;到DropOffMail方法。这是一个重点。

    这就是全部。而已。我希望能为你解决这个问题 - 如果没有,请发表评论,我会尽力澄清。

答案 8 :(得分:17)

这使得回调听起来像方法结束时的return语句。

我不确定它们是什么。

我认为Callbacks实际上是对一个函数的调用,因为另一个函数被调用并完成。

我也认为Callbacks是为了解决原始的调用,在某种程度上“嘿!你要求的东西?我已经完成了 - 只是想我会让你知道 - 回到你身边”。

答案 9 :(得分:16)

之后调用比愚蠢名称回调更好的名称。当函数内或满足条件时,调用另一个函数 Call After 函数,作为参数接收的函数。

不是硬编码函数中的内部函数,而是编写一个函数来接受已经编写的 Call After 函数作为参数。根据接收参数的函数中的代码检测到的状态更改,可能会调用 后调用。

答案 10 :(得分:15)

回调函数是您为现有函数/方法指定的函数,在操作完成时调用,需要额外处理等。

例如,在Javascript或更具体的jQuery中,您可以指定在动画结束时调用的回调参数。

在PHP中,preg_replace_callback()函数允许您提供在匹配正则表达式时调用的函数,并将匹配的字符串作为参数传递。

答案 11 :(得分:13)

  

什么是回调

  • 一般情况下,通过电话回复有人收到的电话。
  • 在计算中,回调是一段可执行代码,作为参数传递给其他代码。当函数完成它的工作时(或当某个事件发生时),它会调用你的回调函数(它会回调你的名字)函数。
  

什么是回调函数

  • 一个回调函数就像一个仆人,当他完成一项任务后“回调”给他的主人。
  • 一个回调函数是一个传递给另一个函数的函数(让我们调用另一个函数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中,回调允许插件模块从容器/环境访问服务。

Analogy: Callbacks. Asynchronous. Non-blocking
Real life example for callback

答案 12 :(得分:10)

查看图片:)this is how it works

主程序使用回调函数名调用库函数(也可能是系统级函数)。此回调函数可能以多种方式实现。主程序根据需要选择一个回调。

最后,库函数在执行期间调用回调函数。

答案 13 :(得分:6)

假设我们有一个函数sort(int *arraytobesorted,void (*algorithmchosen)(void)),它可以接受一个函数指针作为其参数,可以在sort()的实现中的某个点使用。然后,这里函数指针algorithmchosen正在寻址的代码被称为回调函数

看到优势是我们可以选择任何算法:

  1.    algorithmchosen = bubblesort
  2.    algorithmchosen = heapsort
  3.    algorithmchosen = mergesort   ...

例如,已经使用原型实现了这些:

  1.   `void bubblesort(void)`
  2.   `void heapsort(void)`
  3.   `void mergesort(void)`   ...

这是用于在面向对象编程中实现多态的概念

答案 14 :(得分:6)

这个问题的简单答案是回调函数是通过函数指针调用的函数。如果将函数的指针(地址)作为参数传递给另一个函数,当该指针用于调用它所指向的函数时,则表示已进行回调

答案 15 :(得分:3)

“在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用更高级别层中定义的子例程(或函数)。“ - Wikipedia

使用函数指针在C中回调

在C中,使用函数指针实现回调。函数指针 - 顾名思义,是指向函数的指针。

例如,int(* ptrFunc)();

这里,ptrFunc是一个指向函数的指针,该函数不带参数并返回一个整数。不要忘记放入括号,否则编译器将假定ptrFunc是一个普通的函数名,它不需要任何东西并返回一个指向整数的指针。

这是一些演示函数指针的代码。

#include<stdio.h>
int func(int, int);
int main(void)
{
    int result1,result2;
    /* declaring a pointer to a function which takes
       two int arguments and returns an integer as result */
    int (*ptrFunc)(int,int);

    /* assigning ptrFunc to func's address */                    
    ptrFunc=func;

    /* calling func() through explicit dereference */
    result1 = (*ptrFunc)(10,20);

    /* calling func() through implicit dereference */        
    result2 = ptrFunc(10,20);            
    printf("result1 = %d result2 = %d\n",result1,result2);
    return 0;
}

int func(int x, int y)
{
    return x+y;
}

现在让我们尝试使用函数指针理解C中Callback的概念。

完整的程序有三个文件:callback.c,reg_callback.h和reg_callback.c。

/* callback.c */
#include<stdio.h>
#include"reg_callback.h"

/* callback function definition goes here */
void my_callback(void)
{
    printf("inside my_callback\n");
}

int main(void)
{
    /* initialize function pointer to
    my_callback */
    callback ptr_my_callback=my_callback;                        
    printf("This is a program demonstrating function callback\n");
    /* register our callback function */
    register_callback(ptr_my_callback);                          
    printf("back inside main program\n");
    return 0;
}

/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);


/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"

/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
    printf("inside register_callback\n");
    /* calling our callback function my_callback */
    (*ptr_reg_callback)();                               
}

如果我们运行此程序,输出将是

这是一个演示函数回调的程序 在register_callback里面 在my_callback里面 回到主程序中

高层函数将低层函数称为普通调用,回调机制允许低层函数通过指向回调函数的指针调用高层函数。

使用接口进行Java回调

Java没有函数指针的概念 它通过其接口机制实现回调机制 这里代替函数指针,我们声明一个接口有一个方法,当被调用者完成其任务时将调用该方法

让我通过一个例子来证明:

回调界面

public interface Callback
{
    public void notify(Result result);
}

来电者或更高级别的课程

public Class Caller implements Callback
{
Callee ce = new Callee(this); //pass self to the callee

//Other functionality
//Call the Asynctask
ce.doAsynctask();

public void notify(Result result){
//Got the result after the callee has finished the task
//Can do whatever i want with the result
}
}

被叫方或下层功能

public Class Callee {
Callback cb;
Callee(Callback cb){
this.cb = cb;
}

doAsynctask(){
//do the long running task
//get the result
cb.notify(result);//after the task is completed, notify the caller
}
}

使用EventListener模式回调

  • 列表项

此模式用于通知0到n个观察者/听众特定任务已完成

  • 列表项

Callback机制和EventListener / Observer机制之间的区别在于,在回调中,被调用者通知单个调用者,而在Eventlisener / Observer中,被调用者可以通知任何对该事件感兴趣的人(该通知可能会发送给其他人)应用程序中未触发任务的部分)

让我通过一个例子解释一下。

活动界面

public interface Events {

public void clickEvent();
public void longClickEvent();
}

班级小工具

package com.som_itsolutions.training.java.exampleeventlistener;

import java.util.ArrayList;
import java.util.Iterator;

public class Widget implements Events{

    ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>(); 
    ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();

    @Override
    public void clickEvent() {
        // TODO Auto-generated method stub
        Iterator<OnClickEventListener> it = mClickEventListener.iterator();
                while(it.hasNext()){
                    OnClickEventListener li = it.next();
                    li.onClick(this);
                }   
    }
    @Override
    public void longClickEvent() {
        // TODO Auto-generated method stub
        Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();
        while(it.hasNext()){
            OnLongClickEventListener li = it.next();
            li.onLongClick(this);
        }

    }

    public interface OnClickEventListener
    {
        public void onClick (Widget source);
    }

    public interface OnLongClickEventListener
    {
        public void onLongClick (Widget source);
    }

    public void setOnClickEventListner(OnClickEventListener li){
        mClickEventListener.add(li);
    }
    public void setOnLongClickEventListner(OnLongClickEventListener li){
        mLongClickEventListener.add(li);
    }
}

班级按钮

public class Button extends Widget{
private String mButtonText;
public Button (){
} 
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}

班级复选框

public class CheckBox extends Widget{
private boolean checked;
public CheckBox() {
checked = false;
}
public boolean isChecked(){
return (checked == true);
}
public void setCheck(boolean checked){
this.checked = checked;
}
}

活动类

package com.som_itsolutions.training.java.exampleeventlistener;

public class Activity implements Widget.OnClickEventListener
{
    public Button mButton;
    public CheckBox mCheckBox;
    private static Activity mActivityHandler;
    public static Activity getActivityHandle(){
        return mActivityHandler;
    }
    public Activity ()
    {
        mActivityHandler = this;
        mButton = new Button();
        mButton.setOnClickEventListner(this);
        mCheckBox = new CheckBox();
        mCheckBox.setOnClickEventListner(this);
        } 
    public void onClick (Widget source)
    {
        if(source == mButton){
            mButton.setButtonText("Thank you for clicking me...");
            System.out.println(((Button) mButton).getButtonText());
        }
        if(source == mCheckBox){
            if(mCheckBox.isChecked()==false){
                mCheckBox.setCheck(true);
                System.out.println("The checkbox is checked...");
            }
            else{
                mCheckBox.setCheck(false);
                System.out.println("The checkbox is not checked...");
            }       
        }
    }
    public void doSomeWork(Widget source){
        source.clickEvent();
    }   
}

其他类

public class OtherClass implements Widget.OnClickEventListener{
Button mButton;
public OtherClass(){
mButton = Activity.getActivityHandle().mButton;
mButton.setOnClickEventListner(this);//interested in the click event                        //of the button
}
@Override
public void onClick(Widget source) {
if(source == mButton){
System.out.println("Other Class has also received the event notification...");
}
}

主要类

public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Activity a = new Activity();
OtherClass o = new OtherClass();
a.doSomeWork(a.mButton);
a.doSomeWork(a.mCheckBox);
}
}

从上面的代码中可以看出,我们有一个名为events的接口,它基本上列出了我们的应用程序可能发生的所有事件。 Widget类是所有UI组件(如Button,Checkbox)的基类。这些UI组件是实际从框架代码接收事件的对象。 Widget类实现了Events接口,它还有两个嵌套接口,即OnClickEventListener&amp; OnLongClickEventListener

这两个接口负责监听Widget派生的UI组件(如Button或Checkbox)上可能发生的事件。因此,如果我们将此示例与使用Java接口的早期Callback示例进行比较,则这两个接口将用作Callback接口。因此,更高级别的代码(Here Activity)实现了这两个接口。每当窗口小部件发生事件时,将调用更高级别的代码(或更高级代码中实现的这些接口的方法,这是Activity)。

现在让我讨论一下Callback和Eventlistener模式之间的基本区别。正如我们提到的那样,使用Callback,Callee只能通知一个呼叫者。但是在EventListener模式的情况下,Application的任何其他部分或类都可以注册Button或Checkbox上可能发生的事件。这类的例子是OtherClass。如果您看到OtherClass的代码,您会发现它已将自身注册为ClickEvent的侦听器,该侦听器可能出现在Activity中定义的Button中。有趣的是,除了Activity(调用者)之外,每当Button上发生click事件时,也会通知此OtherClass。

答案 16 :(得分:2)

回调函数是您将(作为引用或指针)传递给某个函数或对象的函数。 出于任何目的,此函数或对象将在以后任何时候(可能多次)调用此函数:

  • 通知任务结束
  • 请求两个项目之间的比较(如c qsort())
  • 报告流程进度
  • 通知活动
  • 委派对象的实例
  • 委派区域的绘画

...

因此将回调描述为在另一个函数或任务结束时调用的函数过度简化(即使它是一个常见的用例)。

答案 17 :(得分:1)

回调函数,也称为高阶函数,是作为参数传递给另一个函数的函数,并且在父函数内调用(或执行)回调函数。

$("#button_1").click(function() {
  alert("button 1 Clicked");
});

这里我们将一个函数作为参数传递给click方法。 click方法将调用(或执行)我们传递给它的回调函数。

答案 18 :(得分:1)

一个重要的使用领域是您将一个函数注册为句柄(即回调),然后发送消息/调用某个函数来执行某些工作或处理。现在处理完成后,被调用的函数将调用我们的注册函数(即现在回调已完成),从而表明我们已完成处理。
This维基百科链接以图形方式解释得非常好。

答案 19 :(得分:1)

回调功能 作为参数传递给另一个函数的函数。

function test_function(){       
 alert("Hello world");  
} 

setTimeout(test_function, 2000);

注意:在上面的示例中,test_function用作setTimeout函数的参数。

答案 20 :(得分:1)

回调是将一个函数作为参数传递给另一个函数,并在过程完成后调用该函数的想法。

如果您通过上面的出色答案获得了回调的概念,我建议您应该了解其概念的背景。

“是什么让他们(计算机科学家)开发回调?” 您可能会了解一个正在阻塞的问题。(尤其是阻塞UI) 回调不是唯一的解决方案。 还有很多其他解决方案(例如:线程,期货,承诺...)。