核心结论是 callback 回调函数本质就是把一个函数当作参数,传递给另外一个函数,等到外层函数执行到指定时机之后,再回头调用这个传进去的函数,它是异步编程非常基础的实现方式,在 JavaScript 里使用最为广泛,像定时器 ajax 请求事件监听这类场景底层都用到回调函数的设计思路,理解它的核心就是函数可以像变量一样传递,执行时机不是立刻执行,而是由外层函数在合适的节点触发。之所以要使用 callback 回调函数,是因为很多任务是耗时操作,如果同步等待会阻塞主线程,把后续处理逻辑封装成回调函数,耗时任务完成之后自动执行回调,就可以实现非阻塞执行,但是传统回调也存在回调地狱的痛点,多层嵌套之后代码可读性会变差,后面才衍生出 Promise async await 这类改进方案,下面以 JavaScript 为主,分层讲解基础语法执行流程同步回调异步回调完整代码实例以及常见坑点。
基础概念说明
| 项目 | 内容 |
|---|---|
| 核心定义 | 函数作为参数传入,由宿主函数在特定时机调用 |
| 主流语言 | JavaScript C C++ Python 都支持回调写法 |
| 分类 | 同步回调 异步回调 |
| 典型应用 | 定时器 网络请求 文件读取 事件绑定 |
| 主要缺点 | 多层嵌套容易形成回调地狱 |
同步 callback 回调函数用法实例
同步回调就是外层函数执行的时候,立刻执行传入的回调函数,整个流程是阻塞顺序执行
代码示例
// 定义回调函数
function myCallback(result){
console.log("回调执行,结果是", result)
}
// 主函数接收回调作为参数
function mainFunc(num, callback){
let res = num 2
callback(res)
}
// 调用,把myCallback传进去
mainFunc(10, myCallback)
执行流程
1 定义好 myCallback 函数
2 调用 mainFunc,把 myCallback 这个函数引用作为实参传入
3 mainFunc 内部计算完成之后,主动调用 callback (res)
4 进入 myCallback 内部打印结果
补充说明,数组高阶方法 forEach map filter 内部传入的函数,本质就是同步 callback 回调函数
异步 callback 回调函数用法实例
异步回调是最常用场景,耗时任务完成之后才触发回调,不会卡住后面代码,典型就是 setTimeout
代码示例
function asyncTask(callback){
console.log("开始异步任务")
setTimeout(function(){
let data = "任务完成返回数据"
callback(data)
}, 2000)
console.log("主线程继续执行其他代码")
}
asyncTask(function(res){
console.log("异步callback收到", res)
})
执行顺序说明
第一行打印开始异步任务
接着打印主线程继续执行其他代码
等待 2 秒之后,定时器触发,执行传入的匿名 callback,打印异步返回数据
这个例子可以直观看到,回调不是马上运行,是等到异步事件触发之后才执行
带错误处理的 callback 标准写法实例
前端和 nodejs 开发有约定俗成的错误优先回调规范,回调第一个参数固定是 error,第二个参数才是成功数据
代码示例
function loadData(callback){
let success = true
if(success){
callback(null, "加载成功数据")
}else{
callback(new Error("加载失败"), null)
}
}
loadData(function(err, data){
if(err){
console.log("callback捕获错误", err.message)
return
}
console.log("callback成功拿到数据", data)
})
回调地狱问题实例
多层异步任务嵌套,每一层都写 callback,代码横向缩进越来越深
step1(function(res1){
step2(res1, function(res2){
step3(res2, function(res3){
step4(res3, function(res4){
console.log("全部执行完毕")
})
})
})
})
问题就是代码难以维护,调试困难,所以后续才使用 Promise 或者 async await 替代多层 callback 回调函数
使用注意清单
1 传递回调的时候只写函数名,不要加括号,加括号代表立刻执行函数,不是传递函数引用
2 异步回调里面 this 指向容易丢失,必要时使用 bind 修改 this 绑定
3 回调需要做好错误捕获,否则异步里面抛出错误很难捕获
4 避免无限嵌套,控制 callback 层级,降低回调地狱风险
【相关 FAQ】
问题 1 callback 回调函数用法实例里面为什么传函数不能加括号
回答 1 在 callback 回调函数用法实例当中,传递回调的时候不能加括号,加括号会立即执行函数,得到函数返回值,我们需要传递的是函数引用,等待外层函数后续触发调用 callback。
问题 2 callback 回调函数用法实例里面同步回调和异步回调区别是什么
回答 1 callback 回调函数用法实例里,同步回调是主函数执行期间马上调用回调,代码顺序阻塞执行,异步回调是等待耗时任务完成之后才触发 callback,主线程不会阻塞等待。
问题 3 callback 回调函数用法实例里面回调地狱是什么
回答 1 callback 回调函数用法实例里面回调地狱就是多个异步任务嵌套,每一层都写 callback,缩进层数过多,代码可读性变差,后续可以用 Promise 优化这类 callback 写法。
问题 4 callback 回调函数用法实例是否只能在 JavaScript 使用
回答 1 callback 回调函数用法实例虽然 JavaScript 演示最多,但是 callback 回调函数是通用编程思想,C C++ Python 等支持函数指针或者一等函数的语言,都可以实现 callback 回调函数。
【免责申明】
本文 callback 回调函数用法实例相关代码基于 JavaScript 语法演示,不同编程语言语法细节存在差异,代码仅用于学习演示,生产项目使用请结合对应语言规范做好异常处理,本文不保证所有运行环境兼容。


