注:此文是node.js实战读后的总结。php
在日常的脚本语言中都是同步进行的,好比php,服务器处理多个请求的方法就是并行这些脚本。多任务处理,多线程等等。可是这种处理方式也有一个问题:每个进程或者线程都会耗费大量的系统资源。若是有一种方法能够最大化的利用CPU的计算能力和可用内存以减小资源浪费那就极好了。这样,咱们的node.js就应运而生了。html
上一个node.js最简单的异步编程案例:node
1 var fs = require('fs'); 2 3 var file; 4 5 fs.open( 6 'info.txt','r', 7 function(err,handle){ 8 var buf = new Buffer(100000); 9 fs.read( 10 handle,buf,0,100000,null, 11 function(err,length){ 12 console.log(buf.toString('utf8',0,length)); 13 fs.close(handle,function(){}); 14 }); 15 16 } 17 );
从这个例子咱们就能够看到在异步函数中使用的最多的回调函数,这些回调函数至少包含一个参数,即最后操做的状态(成功仍是失败),通常而言还有第二个参数,即最后操做返回的结果或信息(好比文件句柄,数据库链接,查询到的数据等),一些回调函数可能还包含更多的参数.假设err表明返回的状态参数,则该参数的值通常会有如下几种状况:1.null:表示操做成功,而且会有一个返回值(若是你须要的话).2.一个error对象的实例:一般人们习惯在error对象上添加code字段而且用message字段来保存错误信息(注:这种方式可让咱们写出的非阻塞代码更具可控性)。对上面的代码进行优化加上错误处理:数据库
1 var fs = require('fs'); 2 3 var file; 4 5 fs.open( 6 'info.txt','r', 7 function(err,handle){ 8 //第一种错误处理方式 9 if(err) 10 { 11 console.log("ERROR:"+ err.code + "("+err.message+")"); 12 return; 13 } 14 var buf = new Buffer(100000); 15 fs.read( 16 handle,buf,0,100000,null, 17 function(err,length){ 18 //第二种错误处理方式 19 if(err){ 20 console.log("ERROR:"+err.code+"("+err.message+")"); 21 }else{ 22 console.log(buf.toString('utf8',0,length)); 23 fs.close(handle,function(){}); 24 } 25 26 }); 27 28 } 29 );
可是,在异步处理的过程当中得注意this的用法以及函数做用域的变化,看下面的代码:编程
1 var fs = require('fs'); 2 3 function FileObject(){ 4 this.filename = ''; 5 6 this.file_exists = function(callback){ 7 console.log("About to open:"+this.filename); 8 fs.open(this.filename,'r',function(err,handle){ 9 if(err){ 10 console.log("Can't open:"+ this.filename); 11 callback(err); 12 return; 13 } 14 fs.close(handle,function(){}); 15 callback(null,true); 16 }); 17 }; 18 } 19 20 var fo = new FileObject(); 21 fo.filename = 'info'; 22 fo.file_exists(function(err,results){ 23 if(err){ 24 console.log("ERROR:"+err.code+"("+err.message+")"); 25 return; 26 } 27 console.log("file exists!!!"); 28 });
咱们本来觉得输出的应该是:api
About to open:info数组
Can't open:info服务器
可是实际上确实:网络
About to open:info多线程
Can't open:undefined
ERROR:ENOENT(ENOENT, open 'G:\nodejs\info')
这是为何呢?在咱们的理解中,大多数状况下,当一个函数嵌套在另外一个函数中时,他就会自动继承父函数的做用域,于是就能访问全部的变量了。可是为何咱们嵌套的回调函数却没有出现咱们觉得的输出呢?
这个问题得归结于this关键字和异步回调函数自己。在咱们调用fs.open函数的时候,他会先初始化本身,而后调用底层的操做系统函数(在咱们的代码中,就是打开文件),而且把回调函数插入到node.js的事件队列中去,执行完会当即返回给file_exists函数,而后退出。当fs.open完成任务后,node就会调用该回调函数,但此时,该函数已经再也不拥有FileObject这个类的继承关系了,因此回调函数会从新赋予新的this指针,在这一过程当中咱们就丢失了咱们的FileObject的this指针,故咱们就不能访问咱们的file_name。可是在这个过程当中,回调函数的做用域还保留着。这里关系到nodejs的事件模式(参考资料:http://nodejs.org/docs/latest/api/events.html),这个也是nodejs的一个重要特性,咱们如今很少说。这种错误最多见的解决方法就是把消失的this指针保存在变量中,下面咱们来重写
1 this.file_exists = function(callback){ 2 //用一个变量来储存this指针 3 var self = this; 4 console.log("About to open:"+self.filename); 5 fs.open(this.filename,'r',function(err,handle){ 6 if(err){ 7 console.log("Can't open:"+ self.filename); 8 callback(err); 9 return; 10 } 11 fs.close(handle,function(){}); 12 callback(null,true); 13 }); 14 };
好了,这样咱们的输出就和咱们想象中的同样了,咱们在写代码时必定不要忘了this的变化,否则就可能出现不少bug = = .
好,咱们接着说.咱们都知道Node运行在单线程中,使用事件轮询来调用外部函数和服务。它将回调函数插入事件队列中来等待响应,而且尽快执行回调函数。好,下面咱们来看一个函数,这个函数的功能就是计算两个数组的交叉元素:
1 function compute_intersection(arr1,arr2,callback){ 2 var results = []; 3 for(var i = 0; i<arr1.length; i++) 4 for(var j = 0; j<arr2.length; j++){ 5 if(arr1[i] == arr2[j]){ 6 results.push(arr1[i]); 7 bareak; 8 } 9 } 10 callback(null,true); 11 }
当我数组中的元素特别大时,该函数就会耗费大量的计算时间。咱们知道在单线程模式中,node.js在同一时间只能作一件事,因此这耗费的大量时间将会成为一个问题。而好比其余一些计算哈希。摘要(digest)或者其余一下耗费时间的操做就可能致使应用处于假死的状态。因此说node.js并不适合计算服务器,nodejs更适合常见的网络任务,好比那些须要大量I、O或者须要向其余服务请求的任务,若是须要一个大量计算的服务器,第一个解决方法就是把这些操做迁移到其余服务器上去,而后用nodejs远程调用。但若是只是偶尔执行这种任务,那么还有第二种解决方法,那就是利用全局对象process的nextTick方法,该方法的做用就是告诉系统,我不要执行控制权,你在你空闲的时候执行我给你的函数就好了.
好,异步中的一些陷井和基本已经介绍完了,欢迎你们补充。