[Node.js] 理解 Node.js 事件驱动
台湾朋友盧承億投稿
这篇讲的东西比较偏概念,所以文字叙述会多一点,希望大家可以有耐心的看完~
一、Async(非同步)
Node.js 是一个单线程且异步的语言,异步的 function 会被放进一个 event queue,等其他代码运行完之后才会运行那个 event queue。如果 queue 里面有很多事要做就会依序做,所以不会阻塞线程。
二、setTimeout
Javascript 最简单的 async 函数是 setTimeout,setTimeout会在一定的时间之后执行某个函数,或说是在一定时间之后把那个函数放进 event queue,等其他事情都做完就会就会开始做 event queue 内的事情。
范例1
setTimeout(function(){
console.log('callback');
}, 1000);
console.log('Hello World');
刚开始跑到 setTimeout,系统会设定在 1 秒之后把 function 要做的事放到 event queue,然后就继续跑到下面输出 “Hello World”,输出结束之后这时候事情都做完了。
等了一秒 function 被塞到 event queue,这时候没有事情要做所以就开始运行 event queue 内的事情,然后就输出“callback”。
范例2
setTimeout(function(){
console.log('callback');
}, 1000);
while(true){
var a = 1 * 2;
}
这个跟范例1 不太一样,先跑到 setTimeout,系统会设定在 1 秒之后把 function 要做的事放到 event queue,继续往下跑到while,一直算一些东西,等了一秒 function 被塞到 event queue。这时候因为一直有事情要做,所以虽然console.log('callback')
已经被放到 event queue,但永远不会执行,因为程序要在做完主要的事情才会开始跑 event queue。
三、Callback
因为不知道 async 的那些工作什么时候会完成,但我们又需要等那些工作做完之后才能做某些事情。比如说如果要判断一个文件里面有多少字符,要先读文件,接着再从读到的字符串计算有多少个字符,但偏偏读文件的函数是 async,这时候就需要 callback。
范例1
读文件并计算长度:
var fs = require('fs');
// fs.readFile(filename, callback(err, content))
fs.readFile('test.txt', function(err, content){
var str = content.toString();
console.log(str.length);
console.log('finish');
});
console.log('not finish');
fs 是个 nodejs 的模块,可以用来读文件,跑到fs.readFile
的时候,系统会把读文件这个任务放到 event queue,有空的时候就会去做,如果当下可以系统有空也有可能马上读。
接着就继续往下跑输出not finish
,因为不知道文件什么时候会读完,所以我们把文件读完要做的事情放在 callback 里面。虽然不知道什么时候会读完,但只要一读完文件就会做callback 里面的事情,计算那个文件里面有多少字符,然后输出finish。
范例2
读完文件在档尾加上Hello World
,再写回去文件:
var fs = require('fs');
// fs.readFile(filename, callback(err, content))
// fs.writeFile(filename, content, callback(err))
fs.readFile('test.txt', function(err, content){
var str = content.toString();
str += 'Hello World';
fs.writeFile('test.txt', str, function(err){
console.log('finish');
});
});
console.log('not finish');
用 callback 的方式可以一直迭代,读完文件之后在尾巴加上Hello World
,之后再写入文件。但写入文件也是 async,所以也要设定一个 callback,写入完成之后再输出not finish
。
范例3
在 callback 内检查错误:
var fs = require('fs');
fs.readFile('test.txt', function(err, content){
if(err) console.log(err);
var str = content.toString();
str += 'Hello World';
});
callback 内通常都会有一个err
传回,如果不是null
或undefined
,代表处理过程中有发生错误。比如说要读取test.txt
这个文件,结果根本没有这个文件,那就会发生错误。用 callback 一定要检查错误,如果有错误继续做下去可能会发生非预期的结果。
四、自己写一个 async function
我们也可以把一些 async 的操作包起来,自己写一个 async function,然后定义一个 callback。
范例
写一个函数叫做 append,功能是在某个文件后面加上某个字符串,因为 append 需要读写文件,所以一定也是个 async 函数。
prototype 可能是append(filename, str, callback(err))
var append = function(filename, str, callback){
fs.readFile(filename, function(err1, content){
if(err1) console.log(err1);
var newContent = content.toString() + str;
fs.writeFile(filename, newContent, function(err2){
if(err2) console.log(err2);
callback(err1 || err2);
});
});
};
append('text.txt', 'Hello World', function(err){
if(err) console.log(err);
console.log('finish');
});
在append
内刚开始先读文件接着写文件,等读写都完成之后就呼叫 callback,这样就会运行使用者定义的 callback function。
写完之后就可以直接像下面这样用,然后给一个 callback function,在append
都完成之后就会运行 callback 然后输出finish
。
五、Callback Hell
因为使用 callback 可能会一直往上迭,先进行 A 接着跑 B 然后 C, D 等等等,B 需要 A 的结果,C 需要 B 的结果,写出来的代码可能是:
func1(function(err1, result1){
if(err1){
console.log(err1);
} else {
func2(result1, function(err2, result2, result3){
if(err2){
console.log(err2);
} else {
func3(result2, result3, function(err3, result4){
if(err3){
console.log(err3);
} else {
console.log(result4);
}
});
}
});
}
});
这样的代码还是对的,只是很丑很难debug,可以用 asyncmodule 来改善,避免 code 越迭越高,可以参考(http://larry850806.github.io/2016/05/31/async/)。
不然也可以用Promise,不过那比较难懂,建议完全理解 callback 再开始摸 Promise。
六、总结
整个程序只有一个 event queue,async 的 function 都会被塞到 event queue,系统有空就开始运行 event queue,里面的所有任务会轮流运行,运行完就呼叫 callback。
Javascript 的 callback 机制跟其他语言不一样,像 C++ / Java 都是一行一行运行(如果只有单线程),但 js 因为有一堆 callback 常常不知道现在在运行哪一段代码,刚开始会觉得有点乱,但久了之后觉得还蛮好用的,不用自己去确认某件事做完了没,反正做完会有 callback。
Node.js 的事件驱动机制介绍就到这里。
【读书会往期回顾】
【React启蒙系列文章】
三、[React启蒙系列] React和Babel的基本使用
【您可能感兴趣的文章】
十三、Webpack from First Principles
十四、没时间阅读?佐克伯、比尔盖兹、马斯克教你「5小时原则」
十五、没快速成长,别说你在创业
十六、TCP三次握手&Render Tree页面渲染=>从输入URL到页面显示的过程?
十七、前端知识普及之HTML
二十二、正则之基本入门
二十三、你所不知道的 Console
二十四、谈谈React
二十五、[Javascript] 关于 JS 中的浅拷贝和深拷贝
二十七、前端知识普及之页面加载
前端圈--打造专业的前端技术会议
为web前端开发者提供技术分享和交流的平台
打造一个良好的前端圈生态,推动web标准化的发展
官网:http://fequan.com
微博:fequancom | QQ群:41378087
长按二维码关注我们
投稿:content@fequan.com
赞助合作:apply@fequan.com