回调函数

Posted 四季帆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回调函数相关的知识,希望对你有一定的参考价值。

0. 前言

这篇文章是我以前看别人的微信公众号推文做下的记录,时间有点儿久了,找不到原文链接了,因为现在发表为转载的话需要填写原链接才能发表,所以请原谅我发表为了“原创”。

 

1. 定义

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

 

2. 形象理解:明日油条项目

2.1 假设我们“明日油条”App代码第一版是这样写的:意思很简单,制作好油条后卖出去。

make_youtiao(10000);
sell();

2.2 “明日油条”App代码第二版,使用回调的方法,第二个被make_youtiao函数调用的函数就叫回调,callback。

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

2.3 为什么编写make_youtiao这个小组不直接定义sell函数然后调用呢?

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

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

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

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

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

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

 

3. 异步回调

3.1 同步调用

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

以上功能实现有一个问题,那就是make_youtiao函数的调用方式依然是同步的,也就是说调用方是这样实现的:

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

由于我们要制作10000个油条,make_youtiao函数执行完需要10分钟,调用方依然会被阻塞10分钟,这就是同步调用的问题所在

 

3.2 异步调用

反正调用方并不关心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);
// 立刻返回
// 执行后续流程

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

4. 回调地狱

异步回调这种机制和程序员最熟悉的同步模式不一样,在可理解性上比不过同步,而如果业务逻辑相对复杂,比如我们处理某项任务时不止需要调用一项服务,而是几项甚至十几项,如果这些服务调用都采用异步回调的方式来处理的话,那么很有可能我们就陷入回调地狱中。

举个例子,假设处理某项任务我们需要调用四个服务,每一个服务都需要依赖上一个服务的结果,如果用同步方式来实现的话可能是这样的:

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) {
                ....
            });
        });
    });
});

 

以上是关于回调函数的主要内容,如果未能解决你的问题,请参考以下文章

满足条件时是不是可以在 GLSL 着色器中回调 C/C++ 函数/代码? [关闭]

ios block和delegate的区别

前端片段整理

通过DOM元素数据集将回调传递给js

对onActivityCreated片段回调有啥误解吗?

从片段替换片段时,onRequestPermissionsResult 回调不起作用