Node.js回调函数Node.js异步编程的直接体现就是回调。异步编程依赖于回调,但是不能说使用了回调之后程序就是异步的。回调函数将在任务完成后被调用。Node使用了大量的回调函数,所有的NodeAPI都支持回调函数。例如,我们可以在读取文件的同时执行其他命令。读取文件后,我们将文件内容作为回调函数的参数返回。这种代码执行时不会阻塞或等待文件I/O操作。这大大提高了Node.js的性能,可以处理大量的并发请求。阻塞I/O和非阻塞I/O操作系统内核只有两种I/O方式:阻塞和非阻塞。阻塞式I/O的特点之一就是调用之后,必须等到系统内核层面的所有操作都完成后,调用才会结束。.以读取磁盘上的一段文件为例。系统内核完成磁盘寻道,读取数据,并将数据复制到内存后,调用结束。阻塞式I/O导致CPU等待I/O,浪费等待时间,无法充分利用CPU的处理能力。为了提高性能,内核提供了非阻塞I/O。非阻塞I/O和阻塞I/O的区别在于调用后会立即返回示例阻塞代码实例。创建文件input.txt,内容如下:景欣的小树屋:http://jxdxsw.com/Createmain.js文件,代码如下:varfs=require("fs");vardata=fs.readFileSync('input.txt');console.log(data.toString());console.log("Program执行结束!");上面代码的执行结果如下:$nodemain.js静心的小树屋:http://jxdxsw.com/程序执行完毕!非阻塞代码实例创建一个文件input.txt,内容如下:Jingxin'sSmalltr??eehouse:http://jxdxsw.com/创建一个main.js文件,代码如下:varfs=require("fs");fs.readFile('input.txt',function(err,data){if(err)returnconsole.error(err);console.log(data.toString());});控制台。log("程序执行结束!");以上代码执行结果如下:$nodemain.js程序执行结束!Jingxin的小树屋:http://jxdxsw.com/在上面两个例子中,我们明白了阻塞调用和非阻塞调用的区别。第一个实例在读取文件后完成执行程序。在第二种情况下,我们不需要等待文件被读取,这样就可以在读取文件的同时执行下一段代码,大大提高了程序的性能。所以阻塞是按顺序执行的,非阻塞不需要按顺序执行,所以如果我们需要处理回调函数的参数,就需要写在回调函数中。轮询非阻塞I/O返回后,CPU时间片可以用来处理其他事务,此时的性能提升是很明显的。但是非阻塞I/O也存在一些问题。由于完整的I/O还没有完成,所以立即返回的不是业务层期望的数据,而只是当前调用的状态。为了获取完整的数据,应用程序需要反复调用I/O操作来确认是否完整。这种反复调用以确定操作是否完成的技术称为轮询。详细可以看这几篇Ajax轮询——《通过Ajax定时查询服务器》Node的异步I/O事件循环Node.js是单进程单线程应用,但是通过事件和回调支持并发,所以性能很高。Node.js中的每个API都是异步的,并作为单独的线程运行,使用异步函数调用并处理并发。基本上Node.js中所有的事件机制都是在设计模式中使用观察者模式来实现的。Node.js单线程类似于进入while(true)事件循环,直到没有事件观察者退出。每个异步事件都会生成一个事件观察器。如果事件发生,则调用回调函数。观察者每次执行循环体的过程称为Tick。在每个Tick进程中,如何判断是否有事件需要处理?这就引入了观察者的概念。事件可能来自用户点击或某些文件加载??时,这些产生的事件有相应的观察者。在Node中,事件主要来自网络请求、文件I/O等,这些事件对应的观察者有文件I/O观察者、网络I/O观察者等,观察者对事件进行了分类。事件驱动程序Node.js使用事件驱动模型。当Web服务器收到请求时,它会关闭它并处理它,然后为下一个Web请求提供服务。当请求完成后,将其放回处理队列,当到达队列头部时,将结果返回给用户。该模型非常高效且可扩展,因为网络服务器总是在不等待任何读取或写入操作的情况下接受请求。(这也叫非阻塞IO或事件驱动IO)在事件驱动模型中,会产生一个主循环来监听事件,当检测到事件时会触发回调函数。整个事件驱动流程就是这样实现的,非常简洁。有点类似于观察者模式,一个事件相当于一个主题(Subject),所有注册到这个事件的处理者都相当于观察者(Observer)。Node.js有多个内置事件。我们可以通过引入事件模块并实例化EventEmitter类来绑定和监听事件,如下例所示://导入事件模块varevents=require('events');//创建一个eventEmitter对象vareventEmitter=新事件.EventEmitter();以下程序绑定事件处理程序://绑定事件和事件处理程序eventEmitter.on('eventName',eventHandler);我们可以通过程序触发事件://触发一个事件eventEmitter.emit('eventName');例1:创建一个main.js文件,代码如下://导入事件模块varevents=require('events');//创建一个eventEmitter对象vareventEmitter=newevents.EventEmitter();//创建事件处理器varconnectHandler=functionconnected(){console.log('连接成功。');//触发data_received事件eventEmitter.emit('data_received');}//绑定连接事件处理器eventEmitter.on('connection',connectHandler);//使用匿名函数绑定data_received事件eventEmitter.on('data_received',function(){console.log('数据接收成功');});//触发连接事件eventEmitter.emit('connection');console.log("程序执行完毕。");接下来我们执行上面的代码:$nodemain.js连接成功。数据接收成功。程序执行完毕。事件循环是典型的生产者/消费者模型。异步I/O、网络请求等都是事件的生产者,源源不断地为Node提供不同类型的事件,这些事件被传递给相应的观察者,事件循环从观察者那里获取事件并进行处理。在windows下,这个循环是基于IOCP创建的,而在*nix下是基于多线程的。Node应用程序如何工作?在Node应用程序中,执行异步操作的函数将回调函数作为最后一个参数,回调函数接收一个错误对象作为第一个参数。接下来我们重温一下之前的例子,创建一个input.txt,文件内容如下:景心的小树屋:http://jxdxsw.com/创建一个main.js文件,代码如下:fs.readFile('input.txt',function(err,data){if(err){console.log(err.stack);return;}console.log(data.toString());});console.log("程序执行完成");在上面的程序中,fs.readFile()是一个读取文件的异步函数。如果在读取文件时发生错误,err对象将输出错误消息。如果没有错误,readFile会跳过err对象的输出,通过回调函数输出文件内容。执行上面的代码,执行结果如下:程序执行完后,静心的小树屋:http://jxdxsw.com/接下来我们删除input.txt文件,执行结果如下:程序执行Error:ENOENT,open'input.txt'由于文件input.txt不存在,所以输出了错误信息。非I/O异步APINode还有一些与I/O无关的异步API:setTimeout()setInterval()setImmediate()process.nextTick()setTimeout()和setInterval()与API中的一致浏览器,分别用于单次和多次定时执行任务。它们的实现原理与异步I/O类似,只是不需要I/O线程池的参与。参考Web通信中长连接和长轮询的方法《深入浅出nodejs》
