链接:http://www.runoob.com/nodejs/nodejs-callback.html
首先什么是单线程异步非阻塞?
单线程的意思整个程序从头到尾但是运用一个线程,程序是从上往下执行的。异步操作就是程序虽然是从上到下执行的,但是某个函数执行时间过长时并不会阻塞在那里等待它执行完,然后在执行下面的代码。非阻塞也就是这个意思。
为什么node是异步非阻塞的呢,得力于回调函数,还有js中的定时器也是经典的异步操作。
###4.1 Node.js异步机制 由于异步的高效性,node.js设计之初就考虑做为一个高效的web服务器,作者理所当然地使用了异步机制,并贯穿于整个node.js的编程模型中,新手在使用node.js编程时,往往会羁绊于由于其他编程语言的习惯,比如C/C++,觉得无所适从。我们可以从以下一段简单的睡眠程序代码窥视出他们的区别,下面是摘自《linux程序设计》打印10个时间的C代码:
#include <time.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
int i;
time_t the_time;
for(i = ; i <= ; i++) {
the_time = time((time_t *));
printf("The time is %ld\n", the_time);
sleep();
}
exit();
}
编译后打印结果如下:
The time is 1396492137
The time is 1396492139
The time is 1396492141
The time is 1396492143
The time is 1396492145
The time is 1396492147
The time is 1396492149
The time is 1396492151
The time is 1396492153
The time is 1396492155
从C语言的打印结果可以发现,是隔2秒打印一次,按照C程序该有的逻辑,代码逐行执行。以下Node.js代码本意如同上述C代码,使用目的隔2秒打印一次时间,共打印10条(初次从C/C++转来接触Node.js的程序员可能会写出下面的代码):
function test() {
for (var i = 0; i < 10; i++) {
console.log(new Date);
setTimeout(function(){}, 2000); //睡眠2秒,然后再进行一下次for循环打印
}
};
test();
打印结果: Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)
观察结果发现都是在14:53:22同一个时间点打印的,根本就没有睡眠2秒后再执行下一轮循环打印!这是为什么?从官方的文档我们看出setTimeout是第二个参数表示逝去时间之后在执行第一个参数表示的callback函数,因此我们可以分析, 由于Node.js的异步机制,setTimeout每个for循环到此之后,都注册了一个2秒后执行的回调函数然后立即返回马上执行console.log(new Date),导致了所有打印的时间都是同一个点,因此我们修改for循环的代码如下:
for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(new Date);
}, 2000); }
执行结果如下所示: Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)
神奇,仍然是同一个时间点,见鬼!冷静下来分析,时刻考虑异步,for循环里每次setTimeout注册了2秒之后执行的一个打印时间的回调函数,然后立即返回,再执行setTimeout,如此反复直到for循环结束,因为执行速度太快,导致同一个时间点注册了10个2秒后执行的回调函数,因此导致了2秒后所有回调函数的立即执行。 我们在for循环之前添加console.log("before FOR: " + new Date)和之后console.log("after FOR: " + new Date),来验证我们的推测,打印结果如下(后面省略8条相同的打印行):
before FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中国标准时间)
after FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:42:45 GMT+0800 (中国标准时间)
Thu Apr 03 2014 09:42:45 GMT+0800 (中国标准时间)
…… (省略与上一行8条相同的打印行)
由此可以窥视出Node.js异步机制的端倪了,在for循环中的代码于其后的代码几乎在一个单位秒内完成,而定时器中的回调函数则按要求的2秒之后执行,也是同一秒内执行完毕。那么如何实现最初C语言每隔2秒打印一个系统时间的需求函数呢,我实现了如下一个wsleep函数,放在for循环中,可以达到该目的:
function wsleep(milliSecond) {
var startTime = new Date().getTime();
while(new Date().getTime() <= milliSecond + startTime) {
}
}
但是该函数有一个令他无法在项目中使用的缺陷,请问为什么?
如果没有回调函数的话,就变成阻塞式的,程序基本上是从上到下执行的。主要就是因为回调函数的影响导致的
2.阻塞与非阻塞
Node.js 回调函数
Node.js 异步编程的直接体现就是回调。
异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。
回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。
例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
阻塞代码实例
创建一个文件 input.txt ,内容如下:
菜鸟教程官网地址:www.runoob.com
创建 main.js 文件, 代码如下:
var fs = require("fs"); var data = fs.readFileSync('input.txt'); console.log(data.toString());
console.log("程序执行结束!");
以上代码执行结果如下:
$ node main.js
菜鸟教程官网地址:www.runoob.com 程序执行结束!
非阻塞代码实例
创建一个文件 input.txt ,内容如下:
菜鸟教程官网地址:www.runoob.com
创建 main.js 文件, 代码如下:
var fs = require("fs"); fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
}); console.log("程序执行结束!");
以上代码执行结果如下:
$ node main.js
程序执行结束!
菜鸟教程官网地址:www.runoob.com
以上两个实例我们了解了阻塞与非阻塞调用的不同。第一个实例在文件读取完后才执行完程序。 第二个实例我们呢不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。
因此,阻塞按是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。