一句话彻底理解JS中的回调(Callback)函数
作为JS的核心,回调函数和异步执行是紧密相关的,也是必须跨过去的一道个门槛。
那么究竟什么是回调函数(Callback),其实回调函数并不复杂,明白两个重点即可:
1. 函数可以作为一个参数在传递到另一个函数中。
2. JS是异步编程语言。
简单地说JS代码的执行顺序并不完全是从上至下按部就班完成的。大多数语言都是同步编程语言,比如现在我们有3行代码,那么系统一定是一行一行按顺序向下执行的,第一行执行完了,执行第二行,紧跟着最后执行第三行。你可能会说这不是废话吗?且慢,在JS里则不尽然,比如有3行代码,并不是排在最前面的代码就是最先执行完毕的,很有可能是最后一行语句最先执行完,然后排在最前面的那行反而是最后执行完毕的,所以我们说JS是异步编程语言。
下面以node.js为例,举一个例子保证你在3步之内搞清楚究竟什么叫回调函数:
STEP 1:
以上代码不难理解,就是设置一个全局变量c = 0,然后执行writeFile函数(也就是写入一个文件input.txt),这个函数里面有一行c = 1,函数执行完毕之后再跳出来调用f()函数,f()函数很简单,就是把打印c的值。
按照 “正常” 逻辑:
1. 首先c=02. 然后调用writeFile() 函数 (该函数体内部有一句c = 1)3. 最后再调用f(c),打印c的值
因为调用writeFile()是在f(c)之前,所以c=1这条语句肯定是会被执行到,那么结果应该是1,但是万万想不到,结果竟然是0,明明在writeFile()里我们重新对c进行了赋值,为什么结果还是0呢?
因为程序运行到writeFile()这一行的时候,是一个比较耗时的IO操作(写文件),JS碰到这种操作并不会停在原地一直等待直到函数执行完毕,而是直接执行下一行代码:f(c),而此时,writeFile() 函数内部的 c = 1 其实并没有被执行,所以打印出来的结果还是0 !
那你肯定会说,要解决这个问题还不容易,我们把调用 f(c) 也放进writeFile函数里面不就行了呗!这样就能保证c = 1之后再调用f(c)了吧?想得没错,就这么简单:
STEP 2:
代码的逻辑不需要多说了吧,实在很简单,就是把f(c)放进了writeFile()里面,那么c=1必然会被执行到,然后才执行f(c),不用多说,结果肯定是显示为1。但是改成这样并不完美,因为这么做就相当于将f()\”焊死\”在writeFile()里了,如果此处最终想调用的函数不是f()而是别的其他函数咋整?难不成要写几个不同的writeFile(),而他们之间的区别仅仅是最后调用的那个函数不同?这样也太笨了吧,于是今天的主角:关键字callback登场了。
STEP 3:
经过改造后的代码出现了两次callback关键字,第一个callback出现在writeFile()的参数列表里,起定义的作用,表示这个参数并不是一个普通变量,而是一个函数。也就是前面所说的重点1,即所谓的“以函数为参数”。
第二个callback出现在c = 1下面,表示此处“执行”从参数列表里传递进来的那个函数。这样一来,writeFile()函数在执行完毕之后到底调用哪个函数就变“活”了,如果我们想writeFile()函数执行完之后并不是像第二个例子那样只能调用f(),而是还有别的函数比如说x() y() z(),那么只需要写成 writeFile(x), writeFile(y)… 就行了。
我相信你已经看明白上面的代码,因为实在并不高深,那么我们现在开始用一句话攻略做一个总结:
在大多数编程语言中,函数的形参总是由外往内向函数体传递参数,但在JS里如果形参是关键字\”callback\”则完全相反,它表示函数体在完成某种操作后由内向外调用某个外部函数。
有时候,我们会看到一些函数的形参列表里又出现一个函数定义的情况,初时一头雾水,其实只要你了解了上面的内容,看这种直接在函数调用的时候嵌入一个function的写法会很简单,其本质上仍然是回调函数,因为没有了函数名,所以也称匿名函数。
如本例如果要写成这种风格的话就是长成这样了:
writeFile()函数不变,只是在调用它的时候,直接将函数体嵌在参数列表里了,其作用跟上一个例子完全一样。其实在本例中,fs.writeFile() 函数后面也跟了一个匿名回调函数 function (err) {},这个函数表示当文件写入完毕后,就回调它,如果在写入过程中出现了错误,则通过变量err携带出来。我相信有了前面的铺垫,您应该能理解它的含义了,事实上这种写法在JS里是最常见的主流风格。
万事必有利弊,尽管回调函数很好地解决了异步执行的问题,但是当回调函数不止一层的时候,JS就会出现函数作为参数层层嵌套的可怕场景,导致代码的逻辑难以分析,这就是人们常说的“回调地狱”。因此,为了解决JS的异步执行回调地狱问题,人们又发明了一些其他解决方案,比如说Promises、Async functions等等,当然这又是一个冗长的话题了,在此暂且不表。
【补充】在JS里,当然也并非所有操作都是异步的,比如for循环,无论这个for循环需要耗时多长,系统也一定会等它转完之后才会执行下面的语句,这一点跟其他大部分同步语言是一致的。我所了解的会产生异步执行的操作大概有以下几种:
- 定时器
- 建立网络连接
- 读取网络流数据
- 向文件写入数据
- Ajax提交
- 请求数据库服务
Node.js与C++:napi调用JavaScript回调函数
前情提要 : 前段时间想要为一个基于 Electron 框架开发的应用编写一个原生模块,以便它能够调用 Win32 API。在这个过程中,需要处理涉及到 JavaScript 函数回调的问题,由于接触 Node.js 的时间比较少,还无法理解其中的精髓,因此被这个问题困扰了很久。
NAPI(Node-API)是用于构建 Node.js 原生插件的一种API,旨在提供一个稳定的、跨 Node.js 版本的应用程序二进制接口(ABI),使得开发人员可以编写一次插件代码,并且能够在不同版本的 Node.js 上运行而无需重新编译。它的目标是提供一致的接口,使得编写 Node.js 扩展更加容易。
node-addon-api 是一个仅包含头文件的 C++ 包装类,它简化了基于 C 的 Node-API 的使用。
(如何开发node模块网上已经有很多教程啦,这里就不多赘述啦orz)
开始的时候直接尝试使用Function类,Napi::Function 类提供了一些方法,可用于调用在 JavaScript 中创建并传递给插件的函数。
但是在我的代码中,获取传入回调函数的位置和调用回调函数的位置不在同一个函数中,并且这样写在调用的时候,程序闪退,错误信息为
复制代码
Fatal error in V8: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope
看了一些文档也没有搞明白该怎么解决,尝试1大失败(流泪猫猫头.jpg)
由于回调的问题一直解决不了,之后想做的很多东西就都被搁置了。在一天摸鱼的时候突然看到:异步线程安全函数调用 和 调用线程安全的函数。
- [in] env:调用 API 的环境。
- [in] func:调用的 JavaScript 函数。
- [in] async_resource:与将传递给可能的 async_hooks init 钩子 的异步工作关联的可选对象。
- [in] async_resource_name:一个 JavaScript 字符串,用于为 async_hooks API 公开的诊断信息提供的资源类型提供标识符。
- [in] max_queue_size:队列的最大大小。0 为无限制。
- [in] initial_thread_count:初始获取数,即初始线程数,包括将使用此函数的主线程。
- [in] thread_finalize_data:要传递给 thread_finalize_cb 的可选数据。
- [in] thread_finalize_cb:napi_threadsafe_function 被销毁时调用的可选函数。
- [in] context:附加到生成的 napi_threadsafe_function 的可选数据。
- [in] call_js_cb:可选回调调用 JavaScript 函数以响应不同线程上的调用。详情查看napi_threadsafe_function_call_js
- [out] result:异步线程安全的 JavaScript 函数。
(内心OS:顾名思义,创建一个线程安全的函数,虽然目前对这些参数还不是很清楚,但是现在可以先顺着 napi_threadsafe_function_call_js 这条线索走下去~)
与异步线程安全函数调用一起使用的函数指针。通过调用 napi_call_function,然后调用 JS 函数。
具体函数必须满足:
(内心OS:感觉这个函数像是具体JS回调函数的执行者,那怎么调用回调函数捏)
可以在任何调用JS回调函数的地方调用这个函数。
大概看了一下文档,就开始照猫画虎写自己的代码啦!!
- 创建JS回调函数。(在我的代码中,我的CallbackInfo是一组Object。)
- 创建napi_threadsafe_function_call_js
- 调用JS回调函数
- 在JS中调用原生模块
回调成功的时候简直忍不住在房间里旋转跳跃不停歇hhh
但是这次成功是有点运气在的,因为还是有很多没有解决的问题orz
仅作个人纪录,感谢所有写过相关文章的其他作者!!
请解释什么是回调函数?
在编程中,回调函数是一个函数,当另外一个函数执行完毕后,它将被调用。 回调函数通常作为一个参数,可以将其传递给一个函数,以便在必要的时候调用它。
一个常见的例子是使用回调函数来处理异步操作。 当异步操作完成时,回调函数被调用来处理结果。 这种处理方式可以避免因异步操作导致的阻塞。
一个简单的例子是JavaScript中的setTimeout函数。 这个函数接受两个参数:回调函数和延迟的时间。 当指定的时间到期时,回调函数将被调用。
回调函数是在异步编程中经常用到的一种机制。在异步操作中,可能无法立即得到操作的结果,因此我们需要指定一个回调函数,在操作完成后,将结果传递给它并执行它。
比如:
上面的代码演示了一个模拟异步请求的例子。fetchData函数模拟了一个2秒钟的请求数据的过程,当请求完成后,将数据对象传递给回调函数并执行回调函数。我们通过调用fetchData并传入一个函数,在请求完成后处理返回的数据。
这里再给出两个经典的例子:
- Node.js中的fs.readFile()和fs.writeFile()方法需要在读取或写入文件完成后执行回调函数,以获取读取或写入的数据,如下:
- 微信小程序中的wx.request()方法也需要在请求完成后执行回调函数,以获取请求返回的数据,如下:
这个例子展示了一个向github API请求数据的例子,使用wx.request()方法发送GET请求,当请求完成后,根据请求的结果执行指定的回调函数来处理返回的数据。
总之,回调函数是一个非常通用的编程机制,在异步编程中得到广泛应用。
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。