对于一般的(非异步
)回调函数,函数由我们自行调用:
var froEach = function (list, callback) {
for (let i = 0; i < list.length; i++) {
callback(list[i], i, list)
}
}
而对于Node
中的异步I/O
调用而言,回调函数却不由开发者来调用。
我们从发出调用异步后,到回调被执行,中间发生了什么呢?
从JavaScript发出调用异步,到内核执行异步I/O操作的过渡过程中,存在一种中间产物,它叫做请求对象。
以简单的fs.open()方法来作为例子:
fs.open = function (path, flags, mode, callback) {
// ...
binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback);
};
fs.open()
的作用是根据指定路径和参数去打开一个文件,从而得到一个文件描述符,这是后续I/O
的初始操作
- 从
JavaScript
调用node
的核心模块,核心模块调用C++
内建模块,内建模块通过libuv
进行系统调用,这是node
里经典的调用方法。- 上面图中libuv作为封装层,有两个平台的实现,实质上是调用了
uv_fs_open()
方法。uv_fs_open()
的调用过程中,创建了一个FSReqWrap请求对象。从JavaScript
层传入的参数和当前方法都被封装在这个请求对象中。- 最为关注的回调函数则被设置在这个
FSReqWrap
请求对象的oncomplete_sym属性上。
请求对象是异步I/O过程中重要中间产物,所有的状态都保存在这个对象中,包括送入线程池等待执行以及
I/O
操作完毕后的回调处理。
对象包装完毕后,在windows下,则调用queueUserWorkitm()方法将这个FSReqWrap
请求对象推入线程池中等待执行。
至此,JavaScript调用立即返回,由
JavaScript
层面发起的异步调用的第一阶段就此结束。JavaScript线程可以继续执行当前任务的后续操作。当前的I/O
操作在线程池中等待执行,不管它是否阻塞I/O
,都不会影响到JavaScript线程的后续执行,达到了异步的目的。
网友评论