前言
在 Node.js 探秘(一) 中,我们了解到,Node.js 基于 libuv 实现了 I/O 的异步操作。所以,我们经常写类似下面的代码:
1 2 3 4 5 6 7 |
fs.readFile('test.txt', function(err, data) { if (err) { //error handle/ } //do something with data. }); |
通过回调函数来获得想要的结果。
在我们实际解决问题的时候,往往需要一组操作是有序的,比如:读取配置文件、编写命令行工具等。如果使用回调的方式,会使用很多的回调嵌套,使代码变得很难看。为了解决这个问题,我们引入 Promise、yield 等概念,但今天我们不讨论这些,我们讨论下最简单的解决办法, 同步执行 以及 Node.js 如何在异步的架构上实现同步的方法。
使用同步方法
翻看下 Node.js 的文档,你会看到类似 **Sync
的方法,这些就是 **
对应的同步版本。我们先简单看下如何使用这些方法。
一般读取文件内容,我们会像文章开头那样异步的处理。如果使用同步方法,则类似于下面这样:
1 2 3 4 5 6 |
try { var data = fs.readFileSync('test.txt'); //do something with data. } catch(e) { //error handle. } |
可以看到使用同步方法后,我们需要的数据会在文件操作后直接返回,而不存在 callback 的异步处理。需要注意的是,像上面那样使用同步方法时,异常内容不会在返回值中返回,它会被抛到环境中,需要使用 try...catch
来捕获处理。如果想在返回值中获取异常,可以传入一个 Buffer
实例来储存 raw data,这样返回值就变成了一个数组,第一个元素是字符串形式的结果,第二个元素是 Error 信息。
另外,Node.js 在 v0.12 版本之后,实现了同步进程创建的一系列方法,例如:spawnSync
,execSync
等,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//异步版本 child_process.exec('ls', function(err, data) { if (err) { //error handle } //do something with data }); //同步版本 try { var data = child_process.execSync('ls'); //do something with data } catch(e) { //error handle } |
如何调试 Node.js 源码
在分析具体内容之前,我们先来做些准备工作。debug 是我们在开发时常用的手段,如果我们在看源码的时候,也能边看,边运行,然后在自己想要的地方停下来,或者按照自己的理解稍作修改,再运行,那一定会大大提高效率,下面笔者介绍下自己的方案(以 MacOS为例):
- 首先需要有一份 Node.js 的源码,使用
git clone
Github 的仓库或者去这里下载压缩包,都可以。 - (解压之后)进入源码目录,运行
12./configuremake -j-j [N],–jobs[=N]
参数用来提高编译效率,充分利用多核处理器的性能,并行编译,N 为并行的任务数。但它并不是万能的,如果有编译依赖,最好还是用单核编译。 - 使用你习惯的 IDE 来进行 debug,笔者使用的是 CLion。Debug 配置如下图所示:
将执行文件指定到源码目录/out/Release/
目录下的 node 执行文件,参数为你需要运行的脚本和相应的参数(比如这里我配置了可以手动调用gc
),之后去掉 Before Launch 中的 build。 - 然后运行 Debug 模式,就可以通过断点和 LLDB 来调试 Node.js 源码了,如图。
- 如果需要更改源码,在更改后再次运行
make
即可。
进入正题
从 Node.js API 文档中,可以看到,只有 File System 和 Child Process 中有同步的方法,但是两个模块的同步方法实现是不一样的,我们分开来说。
File System
File System 相关的方法实现都在 lib/fs.js 和 src/node_file.cc 中可以找到。每一个对应的文件操作方法,比如 read、write、link 等等,都有对应的封装。下面以 read
为例进行讲解:
在 lib/fs.js 中我们可以看到,fs.read
(#L587) 和 fs.readSync
(#L633) 两个方法实际是很相似的,只不过在调用 C/C++ 层面的 read
方法时,传入的参数不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//异步方法 fs.read = function(fd, buffer, offset, length, position, callback) { //参数处理 ... var req = new FSReqWrap(); req.oncomplete = wrapper; binding.read(fd, buffer, offset, length, position, req); } //同步方法 fs.readSync = function(fd, buffer, offset, length, position, callback) { //参数处理 ... var r = binding.read(fd, buffer, offset, length, position); ... 异步操作。所以,我们经常写类似下面的代码:
通过回调函数来获得想要的结果。 在我们实际解决问题的时候,往往需要一组操作是有序的,比如:读取配置文件、编写命令行工具等。如果使用回调的方式,会使用很多的回调嵌套,使代码变得很难看。为了解决这个问题,我们引入 Promise、yield 等概念,但今天我们不讨论这些,我们讨论下最简单的解决办法, 同步执行 以及 Node.js 如何在异步的架构上实现同步的方法。 使用同步方法翻看下 Node.js 的文档,你会看到类似 一般读取文件内容,我们会像文章开头那样异步的处理。如果使用同步方法,则类似于下面这样:
可以看到使用同步方法后,我们需要的数据会在文件操作后直接返回,而不存在 callback 的异步处理。需要注意的是,像上面那样使用同步方法时,异常内容不会在返回值中返回,它会被抛到环境中,需要使用 另外,Node.js 在 v0.12 版本之后,实现了同步进程创建的一系列方法,例如:
如何调试 Node.js 源码在分析具体内容之前,我们先来做些准备工作。debug 是我们在开发时常用的手段,如果我们在看源码的时候,也能边看,边运行,然后在自己想要的地方停下来,或者按照自己的理解稍作修改,再运行,那一定会大大提高效率,下面笔者介绍下自己的方案(以 MacOS为例):
进入正题从 Node.js API 文档中,可以看到,只有 File System 和 Child Process 中有同步的方法,但是两个模块的同步方法实现是不一样的,我们分开来说。 File SystemFile System 相关的方法实现都在 lib/fs.js 和 src/node_file.cc 中可以找到。每一个对应的文件操作方法,比如 read、write、link 等等,都有对应的封装。下面以 在 lib/fs.js 中我们可以看到,
|