async模块的异步处理
发布于 10 年前 作者 yyrdl 36539 次浏览 最后一次编辑是 8 年前 来自 分享

node 的运行机制是异步处理,像var fs=require(‘fs’);fs.open(path,‘r’,callback(err,file));…这样的代码node并不会等待fs.open将文件打开准备好数据之后才往下执行,而是直接一扫而过,只是注册了一个事件,告诉系统等fs.open实际工作完之后调用回调函数继续处理,所以fs.read(file,buffer,0,length,null,callback(err,length))写在fs.open的回调函数里,而当异步处理遇到for循环的话,就有麻烦了,

 function(path,callback){
        fs.readdir(path,function(err,files){
       if(err)
      {
        callback(err);
       return;
      }
      var result=[];
     for(var i=0;i<files.length;i++)//这个for想将指定目录下的非文件夹给剔除
     //只保留文件夹名到result中
    {
    fs.stat(path+"/"+files[i],function(err,stats){
    if(stats.isDirectory())
    result.push(files[i]);
    }
    callback(null,result);
   )
   }
  })
 } 

上面这个函数表面上看上去一点错都没有,其实运行是得不到结果的,for循环中的i三俩下就自加到files.length了,fs.stat有运行到但因为是异步,当i=files.length时fs.stat其实还未工作完,程序仍然向下执行,有可能当fs.stat工作完,得到result了后,result都已经无用了,因为,程序已经执行完需要result数据的那步。

人们想到的办法是采用递归调用取代for,

所以上面的for可以写成:

(function  iterator(index){
    if(index==files.length)
    {
       callback(null,result);
       return ;
	   }
fs.stat(path+'/'+files[index],function(err,stas){
   if(err){
callback(err);
return;
}
if(stats.isDirectory())
 {
result.push(files[index]);
   iterator(++index);
}})
 })(0);

由于下一步是当异步函数返回结果之后才调用,所以能很好的按顺序执行,而且能保证在result准备好之后才调用回调函数。

但这样做的麻烦是回递归深的话,容易把人弄糊涂,尤其是不同的递归又嵌套另外的递归的时候,使得代码难以解读。

async模块提供了很好的解决办法,

var async=require('async');

首先看waterfall,顾名思义,像流水线一样有序执行:

async.waterfall([
 function (callback){var a=5;console.log('OK1'); setTimeout(function (){callback(null,a*2)},1000);},
 function(data,callback){console.log('OK2');setTimeout(function(){callback(null,data+7,data)},2000);},
function(result1,result2,callback){console.log('OK3');setTimeout(function (){callback(null,result1-9,result2)},3000);}
],function(err,kk,jj){console.log(kk);console.log(jj);}),

格式为async.waterfall( [], finalFunction),将异步函数作为数组元素放进去,然后会按顺序执行,即使他是异步函数,也会等他把所有的操作完成后才会执行下一个函数,由上面的例子可以看出,数组里面的每一个函数都有一个callback回调函数,他们并不是指代同一个函数,而是下一个将要执行的函数,所以第一个函数的callback就是他后面的那个函数,自然数组最后一个函数的callback是finalFunction,对应递归写法的callback。除了这点还有一点需要注意,callback的第一个参数表示错误的信息,即使没错误,也要填上null,后面才是你想要传递的数据,而且除了finalFunction都不能显示接收错误信息,虽然callback有显式传递,waterfall的运行机制是只要数组中的任意一个函数出错立马跳到finalFunction,finalFunction会接收错误信息

再看async.series([],finalfunction(err,data));

async.series([
 function(callback){setTimeout(function(){callback(null,[1,2])},2000)},
function(callback){setTimeout(function(){callback(null,['OK','hello'])},3000)}],
function(err,result)
{console.log(result);}
 );

与waterfall的不同点是,每一个函数执行的结果不是传递到下一个函数中,也就是说callback都是指代finalFunction,而且要显式传递错误信息, 每一个函数的执行结果都会有序保存到一个数组中,而finalFunction的第二个参数就会接收这个数组。可见这个处理的异步场景与waterfall不同,waterfall数组里面的后一个函数依赖于前一个函数的执行结果,而series则不。另外series可以将异步函数放在对象里,不一定是数组比如:

async.series({string:function(callback){…},number:function(callback){…}},finalFunction(err,data){…}),这样的话data接收的就是一个对象

下面到async.parallel了

这个函数和series只有一点不同,就是数组或对象里的函数(也可以像series以对象的形式)不是一个接一个的执行,即使是异步函数,程序也不会等上一个完全执行完才执行下一个,而是同步处理,结果也是在一个数组或对象中

接着来async.auto({},finalFunction(err,data){…}),这个和series有点像,他更强大,能够让我们将顺序执行和非顺序执行的函数混在一起。仅以一个例子来说明:

async.auto({
result1:function(callback){setTimeout(function(){callback(null,'functionResult1')},2000)},
result2:function(callback){setTimeout(function(){callback(null,'functionResult2')},1000)},
result3:['result1','result2',function(callback,replyData){setTimeout(function(){callback(null,replyData.result1+replyData.result2);},1000)}]
},function(err,data){console.log(data)});

结果输出为: { result2:functionResult2, result1:functionResult1, result3:functionResult1functionResult2 } 注意结果,结果是一个对象,就像输入是函数组成的对象一样,对象元素出现的先后顺序决定于哪个结果先得到,当然依赖于另一个函数的计算结果的函数的结果当然就在其所依赖的后面。

函数的计算值即对应属性的值,同时属性名也充当标签的作用,上面那个例子中result1与result2是独立的,result3依赖于result1和result2,其格式如例子所示,注意result3的执行函数的参数输入与参数引用!!!

最后看看async.eachSeries(array,function(ele,callback),function(err))这家伙的第一个参数是一个数组,数组里面放的是你依次想处理的数据,第二个函数就是对这些数据共同的处理规则,ele指代当前处理的数据,callback指代function(err),每处理完一个数据都必须调用callback以返回信息,无错误则返回null,数据是一个接一个有序处理的,如果当是async.each的话,参数设置都与async.eachSeries相同,唯一不同的是数据并不是一个接一个处理的不能保证处理的顺序。。另外eachSeries也写成forEachSeries,且执行结果貌似先存在一个栈中,最后返回到一个数组里,猜测这个函数的实现也是递归。。。,所以最终的结果和输入的数组是反着对应的:

var tt=async.eachSeries;

var result=[];

tt([1,3,2],function(ele,callback){ callback(null);result.push(ele*ele);},function(err){console.log(err)})

console.log(result.toString()); 结果为[4,9,1] 更多信息参考 https://github.com/caolan/async 另外吐槽一下为什么这里不用富文本编辑器。。。。。。。。。!!!!!!!!!!!!!有图版本可去我的博客看:http://lsdrzj.lofter.com/

6 回复

如果你知道使用markdown的话, 这里的效果会比你博客上还好.

原来我也是习惯使用async, 但是后来我了解了promise后, 现在更喜欢使用bluebird

@jeremial 我这暑假刚开始学node还未接触到blueBird,亲这么喜欢一定很不错,改天试试! 至于使用markdown的话我只能说真不愧为只属于程序猿的社区。@@

感谢分享 另外require貌似是同步加载的哈

@showen 模块是应该同步加载啊

forEach和each是一样的?

promise配合yield可以达到类似协程的效果 这确实是正确的方向

web端还是看实际场景吧 如果用async方便就用async 用promise方便就用promise

回到顶部