我正在探索node.js很长一段时间了。但还没有弄清楚程序是如何执行的。
例如采取这个简单的程序:
// file app.js`
var fs = require('fs');
var fileBuffer = fs.readFileSync('./sample.txt');
fs.readFile('./resource.json', function (er, data) {
console.log(data);
});
console.log(fileBuffer);
现在,当我们输入node app.js
(在命令行上)时,幕后会发生什么,直到程序结束。
具体来说,我想了解:
答案 0 :(得分:4)
阻止I / O只是:在完成之前调用不会返回。因此,当您调用readFileSync
时,它会直接调用OS API来读取文件,并且只有在将整个内容复制到Buffer
时才会控制返回到您的JS。
使用阻塞(同步)函数,事件循环与I / O的处理方式完全无关。这就是为什么同步I / O在节点中非常糟糕 - 它会阻止其他任何事情发生,因为事件循环没有运行。
另一方面,像readFile
这样的普通(异步)I / O函数只是放入I / O请求,然后返回。具体如何执行I / O取决于I / O请求的性质(即文件系统或网络),但它全部由C库libuv在单独的线程上处理。
网络I / O实际上是使用主机操作系统的原生asynchronous I/O facilities执行的,非常快不需要为每个连接使用单独的线程。
每当操作系统报告网络I / O活动时,事件就会被放入队列中。在事件循环的下一个标记处,事件被拾取并分派到适当的JavaScript函数。 (C代码可以获取对JS函数的引用并调用它们。)
使用常规阻塞系统I / O调用完成文件I / O.每个I / O请求都放在libuv work queue中并由线程池执行。这里的关键是阻塞I / O在C语言中完成,位于与JavaScript线程分开的线程上。
当阻塞I / O调用返回时,结果将放入队列中。同样,该事件将在下一个时间点发送。
Node保留待处理工作请求的引用计数 - 例如I / O,计时器和侦听服务器。如果在勾选结束时归零,则该过程退出。 (在某些情况下,您可以使用unref()
从引用计数中明确删除活动请求。)
其他一些相关答案将有助于进一步解释其中的一些概念:
最后,让我们来看看你的示例程序。这里到底发生了什么:
require('fs')
为您提供已初始化的fs模块的参考。内置模块很特殊,不需要加载I / O. fs.readFileSync
调用操作系统文件API。在将文件内容复制到内存之前,它不会返回。fs.readFile
将一个项目(包含文件名和对回调的引用)添加到libuv工作队列,然后立即返回。 (因为工作队列中没有任何内容,I / O将很快在一个单独的线程上开始。)fileBuffer
的内容将记录到控制台。readFile
的回调函数。data
的内容将记录到控制台,并且回调函数将返回。答案 1 :(得分:1)
null
或Error
实例,另一个是数据。此方法接收Buffer
作为第二个参数。将缓冲区转换为字符串的快捷方式是data.toString()
,它将字节列表转换为UTF-8编码字符串。您会注意到几乎每个Node模块都使用此回调签名;一个可选的Error
,然后是数据。
从命令行运行时,您会注意到应用程序在最后一次IO操作完成时退出。 Node会跟踪自己。构建Web应用程序时,打开的HTTP(S)连接将使进程“忙碌”。