刚和朋友吃完饭,回来接着写 ,异步,是node 中一个很重要的概念,可以说对于前端想要转到后台的来说(我这里说的是没有接触过后台的人来说),路由和异步还有包括node是如何建立web服务呈递页面的,这些东西很难转变过来,特别对于经常写js的人来说,来做node的话,可能经常就分不清楚哪里写的是前端代码,那些写的是后台代码了,这是思维模式方面的转变,这个的确很难,特别如果前端接触一段时间node会特别有感触。
我本人最早是学。net后台的,之后慢点接触前端,学习js的一些技术,包括一些框架jquery,ng(angular等)还有bootstrap等,之后接触到的node,对于我来说node中最大的不同之处就是在异步这一块,node中的很多东西基本都是异步的,所以如果你不理解异步的话,很难深入学习node,可以说,这也是node思想的精髓所在,不然node的单线程是无法实现高并发的,所以想要学node,异步必须要了解。
其实我之前写的用fs模块读取html文件的时候,这个fs模块的读取文件的过程就是异步的,当node遇到一个I/O的时候,他不会停下等这个I/O操作网,再去执行其他代码,而是继续去执行别的代码,等到I/O操作完成),会触发执行完的事前,调用执行完成时的回调函数,所以在这里我们也可以理解node中的事件驱动,event loop,非阻塞I/O等概念了。
其实在咱们之前学的js中,就有异步的概念,我在ajax异步简介这篇随笔中还提到了的,那么咱们可以简单的看下异步的原理
var a=1; setTimeout(function(){ console.log(a);//2 }, 30000); console.log(a);//1 a++;
从上面可以看出,当js遇到定时器的时候,上面settimeout定时器定义了一个定时器,3秒后触发内面的方法,当js遇到这个定时器的时候,js不会到这里停3秒,等到定时器里的方法触发,而是继续执行下面的代码,等到3秒到后,触发执行定时器内的方法,上面这个例子可以简单的理解下异步的概念(不一定严谨,哈哈)。
当然这是js中的异步的一个简单案例,可以这么说node的牛逼的地方很大程度上在于异步上,刘邦说,成也萧何,败也萧何。。那么我们也可以说node成也异步,败也异步(当然node还是很厉害的哈,至少在移动端盛行的今天,高并发的时后,node还是发挥了很重要的作用,手机淘宝听说在上层就是用的node来处理高并发的,当然业务层可能是php,底层可能还是java和mysql等),好了,咱们回归正题。
我之前说道,如果是老牌语言的后台来学node,其他都很好理解,就是异步这块特别别扭,因为业务始终是同步的,而在node中很多操作都是异步执行,这可能会导致某个业务会有很多层的嵌套,在代码层面就是一层回调套一层回调,回调里面在套回调,这样从老牌后台语言转到node的人,就会感觉特别扭,而且业务复杂了后,代码将也会很复杂(但是根本还是思想的转变)。
好,我们来看下这样的一个例子(看下node的异步)
var http = require(‘http‘); var fs = require(‘fs‘); var server = http.createServer(function(req,res){ //我们做这样一个场景,模仿用户同时登录系统,看看node中关于事件执行的机制和异步的I/O //从这个中可以看到node中事件是如何工作,node是如何在单线程的条件下有高度的并发的特点的 var randomNum = Math.random()*1000 + 3212; console.log(‘欢迎用户user_‘+randomNum); fs.readFile(‘test/1.txt‘,function(err,data){ console.log(‘用户_user‘+randomNum+‘读取完毕‘); }) res.end(); }); server.listen(3010,‘127.0.0.1‘);
我们然后不断的刷新浏览器,观察控制台中的输出,
很明显我们看到了,显示欢饮用户4119后没有立马显示4119读取完毕,而是欢迎用户3776,然后再是用户4119读取文件,所以这里我们很明显可以看到这里的异步操作,用户4119读取文件时,服务并没有阻塞,而是继续相应用户3776的请求,所以这就是node的非阻塞I/O,这也是node能够实现高并发的依赖。
上述例子我们看到了fs的读取数据的过程是异步的,所以我们要获取读取文件的信息,就必须在fs的回调函数中写代码,这和我之前在。net或php中的做法完全不同,在。net或php中都是通过摸个对象或方法,输入文件路径,和其他参数,然后返回给你一个字符串(文本的数据);
看到这样一个api,于是直接撸代码,开干(app.js)
var fs = require(‘fs‘); fs.readdir(‘./album‘,function(err,files){ if(err){ //获取目录下的文件信息失败 return; } //获取目录下的所有文件成功,files中包含了album文件夹下所有的文件信息 var directorys = []; for (var i = 0; i < files.length; i++) { var file = files[i]; fs.stat(‘./album/‘+file,function(err,stats){ if(stats.isDirectory()){ //是一个文件夹 directorys.push(files[i]); console.log(directorys); } }) }; })
输出
很明显,这不是我们想要的结果,那么为什么会出现两个undefined的情况列,如果我们好好想想就明白了,fs模块的操作是异步的,而我在这里再for循环中嵌套了fs的操作,所以还没等fs的操作完成,for循环就搞完了,这样最后得到的i的值就是length,超出了files的界限了,files【i】自然就是=undefined。但是这几本就是老牌后台程序员的思维,所以我才认为这是对由其他后台转node的,需要转变的思维的地方。(我之前也晕了好久,但是慢点多写点就好了)
那么正确的及解决方案是怎样的了,我在这里使用的是立即函数+递归实现类似于迭代器的功能,来实现的(简单的说,就是强行将异步转变同步,),上代码体会就懂了话不多说
var fs = require(‘fs‘); fs.readdir(‘./album‘,function(err,files){ if(err){ //获取目录下的文件信息失败 return; } //获取目录下的所有文件成功,files中包含了album文件夹下所有的文件信息 var directorys = []; /*for (var i = 0; i < files.length; i++) { var file = files[i]; fs.stat(‘./album/‘+file,function(err,stats){ if(stats.isDirectory()){ //是一个文件夹 directorys.push(files[i]); console.log(directorys); } }) };*/ (function getDirectory(i){ if(i == files.length){ //读取完毕 console.log(directorys); return; } var file = files[i]; fs.stat(‘./album/‘+file,function(err,stats){ if(stats.isDirectory()){ //是一个文件夹 directorys.push(files[i]); } getDirectory(i+1); }) })(0) })
输出
纵欲得到了正确的结果,真的是太艰辛了,当然上面这种用立即函数的形式在前端中也经常会看到,这是我的解决方式,虽然,node中也有同步方法,在node的官网可以看到,node在很多异步操作的后面都要提供了相应的同步的操作,但是我始终感觉,node这样做,是不是与自己的理念有点背道而驰(当然了,个人见解,高手勿喷,哈哈哈哈)。。
这就是我自己对异步的一点理解,当然我的理解都是从代码层面上来说明的,可能有点浅显(个人能力尚浅),我感觉只有理解好这些后,才能保证你的node代码继续写下去,继续学下去,否则很可能学到一定程度你就想地放弃node了,(因为前端用node可能更多的用来构建前端自动化和包管理方面),等你真真涉及后台的一些思想和技术时,你就会发现有很大的不同的