作为传统的同步多线程服务器的备选,异步事件IO被很多企业评估。异步意味着开发者需要学习新模式,忘掉老模式。转换模式时需要忍受严重的大脑重新搭线,说不定电击疗法对此改变有帮助。
重布线
利用node工作,最基础的是需要理解异步编程模式。我准备把异步代码和同步代码放在一起,对比的方式来学习新模式。案例都使用了fs模块,因为它同时实现了同步和异步的两种风格的库函数。
回调
在node中,callback函数是异步事件驱动编码的基本构造块。它是作为参数传递给异步io操作的函数。它们在io操作完成后会被调用。比如fs模块的readdir()就是一个异步io函数,它第一个参数为目录名,第二个参数是一个callback 。当readdir()执行完毕,得到结果后,会调用这个callback,把结果经由callback的参数,传递给callback回调内。
依赖代码和独立代码
下面的案例要读取当前目录的文件清单,打印文件名称,读出当前进程id。
同步版本:
1 2 3 4 5 6 7 8 9 10 11 12 |
var fs = require('fs'), filenames, i, processId; filenames = fs.readdirSync("."); for (i = 0; i < filenames.length; i++) { console.log(filenames[i]); } console.log("Ready."); processId = process.getuid(); |
异步版本:
1 2 3 4 5 6 7 8 9 10 11 12 |
var fs = require('fs'), processId; fs.readdir(".", function (err, filenames) { var i; for (i = 0; i < filenames.length; i++) { console.log(filenames[i]); } console.log("Ready."); }); processId = process.getuid(); |
同步版本案例中,代码等待 fs.readdirSync() I/O 完成。和我们日常的代码类似。
打印文件名的代码是依赖于fs.readdirSync()的结果的,而获取进程id则独立于此输出。因此它们在新的异步版本代码内需要放到不同位置。规则是把依赖代码放到callback内,把独立代码放到原来的位置不动。
次序
同步代码的标准模式是线性的,几行代码需要一个接一个的顺序执行,因为每一个依赖于上一行的输出。如下案例中,代码首先修改文件访问模式(类似unix chmod 命令)、然后重命名文件、然后检查被命名文件是否为符号链接。显然如果代码不能按次序执行;或者文件在模式被修改前被重命名;或者检查符号链接代码在文件被命名前完成;这些都会导致错误。
同步:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var fs = require('fs'), oldFilename, newFilename, isSymLink; oldFilename = "./processId.txt"; newFilename = "./processIdOld.txt"; fs.chmodSync(oldFilename, 777); fs.renameSync(oldFilename, newFilename); isSymLink = fs.lstatSync(newFilename).isSymbolicLink(); |
异步:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var fs = require('fs'), oldFilename, newFilename; oldFilename = "./processId.txt"; newFilename = "./processIdOld.txt"; fs.chmod(oldFilename, 777, function (err) { fs.rename(oldFilename, newFilename, function (err) { fs.lstat(newFilename, function (err, stats) { var isSymLink = stats.isSymbolicLink(); }); }); }); |
异步代码中,这个代码的执行次序被翻译为嵌套的callback。fs.lstat 回调被嵌套在fs.rename 回调内,fs.rename 回调被嵌入到fs.chmod()回调内