回调:指的是将一个函数作为参数传递给另外一个函数,并且通常在第一个函数执行完成以后被调用。把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。Node.js 异步编程的直接体现就是回调,回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有API 都支持回调函数。
我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
阻塞代码实例
创建一个文件 foo.txt ,内容如下,注意保存的时候编码为UTF-8,这样才能在后续Node.js 输出中,显示中文。
我在<a target=_blank href="http://www.csdn.net">www.csdn.net</a> 上写博客
创建 main.js 文件(这里也要保存为UTF-8,不然显示中文也是乱码),代码如下:
var fs = require("fs");
var data = fs.readFileSync('foo.txt');
console.log(data.toString());
node main.js
程序执行结束!
菜鸟教程官网地址:www.runoob.com
console.log("程序执行结束!");
以上代码执行结果如下:
node main.js
我在www.csdn.net 上写博客
程序执行结束!
在程序调用执行node main.js的时候,这里要注意将main,js 与foo.txt文件放在同一个目录下面,不然会因为找不到foo.txt文件,而导致程序运行失败。
在这个代码中,我们可以看到程序先读取foo.txt文件的内容,然后再输出:程序执行结束!,如果foo.txt比较大,读取会花费很多时间,后面的输出会等待很长的时间,直到读取文件完成,才会执行下面的:程序执行结束!这就是阻塞的示例,必须要等到前面的动作执行完成。
非阻塞代码实例
将刚才的main.js 文件,修改为如下代码:
var fs = require("fs");
fs.readFile('foo.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});
console.log("程序执行结束!");
以上代码执行结果如下:
node main.js
程序执行结束!
我在<a target=_blank href="http://www.csdn.net">www.csdn.net</a> 上写博客
此时程序先输出后面的:程序执行结束!,然后再输出在foo.txt文档中读取的内容,以上的例子,并没有等待foo.txt文件读取完成,而是采用非阻塞的办法,直接执行程序下面的代码,我们可以执行读取文件下面的内容,而不用去等待读取文件的结束,这就是非阻塞。
以上两个例子,我们可以看出,阻塞按是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。
当然如果在Node.js 中,我们需要采用阻塞的方式调用函数,那么我们也可以通过在回调函数中嵌套回调函数即可。
这里同步和阻塞这两个术语可以互换使用,指的是代码的执行会在函数返回之前停止,就如在前面的示例所看到的,如果某个操作阻塞,那么脚本就无法继续。对于用户而言,这就意味着必须等待。异步和非阻塞这两个术语也可以相互互换,指的是基于回调的、允许脚本并行执行操作的方法。脚本无需等待某个操作的结果才能继续前进,因为操作结果会在事件发生时有回调来处理,使用异步方法,操作无需一个接一个地发生。
事件循环
现在,读者可能会问,这些神奇的事件是如何发生的?Node.js使用Javacript的事件循环来支持它所推崇的异步编程风格。这可能又是一个难以掌握的概念。基本上,事件循环使得系统可以将回调函数先保存起来,而后当事件在将来发生时再运行。这可以是数据库返回数据,也可以是HTTP请求返回数据。因为回调函数的执行被推迟到事件发生之后,于是,就无需停止执行,控制流可以返回到Node运行时的环境,从而让其他事情继续发生。如本文中所看到的阻塞和非阻塞示例那样,使用事件虚幻是另外一种编程方式,有些开发人员称其为编程将里面翻到外面的程序,实际上关键思想是,将代码围绕着事件来架构而不是按照期待中的输入顺序来架构,由于事件循环以单一进程为基础,所以为了确保高新能需要遵循以下的规则:
1、函数必须快速返回。
2、函数不得阻塞。
3、长时间运行的操作必须移到另外一个进程中。
在这样的上下文中,有些程序不适合于事件循环,如果程序或函数需要长时间才能完成处理,那么事件循环就不是一个好的选择。