Nodejs async模块的异步处理

Nodejs async模块的异步处理

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 回复

Node.js Async模块的异步处理

Node.js 的运行机制是基于事件驱动和非阻塞 I/O,这意味着它不会等待异步操作完成后再执行后续代码。这在处理文件读取、网络请求等I/O密集型任务时非常有用,但也可能带来一些挑战,特别是在处理循环中的异步操作时。

异步循环问题

考虑以下函数,目的是从指定目录中获取所有文件夹名称:

function getFolders(path, callback) {
    fs.readdir(path, function(err, files) {
        if (err) {
            callback(err);
            return;
        }

        var result = [];
        for (var i = 0; i < files.length; i++) {
            fs.stat(path + "/" + files[i], function(err, stats) {
                if (stats.isDirectory()) {
                    result.push(files[i]);
                }
            });
        }
        callback(null, result);
    });
}

这个函数看起来没什么问题,但实际上,由于 fs.stat 是异步操作,for 循环中的 i 变量会在 fs.stat 执行之前就已经自增到 files.length,导致 result 数组在所有异步操作完成之前就被传递给 callback 函数。

解决方案:递归调用

一种解决方案是使用递归调用来确保每个异步操作完成后再进行下一步:

function getFolders(path, callback) {
    fs.readdir(path, function(err, files) {
        if (err) {
            callback(err);
            return;
        }

        var result = [];

        (function iterator(index) {
            if (index === files.length) {
                callback(null, result);
                return;
            }

            fs.stat(path + "/" + files[index], function(err, stats) {
                if (stats.isDirectory()) {
                    result.push(files[index]);
                }
                iterator(index + 1);
            });
        })(0);
    });
}

这种方法虽然有效,但递归调用可能会变得复杂且难以理解,特别是当嵌套递归调用较多时。

使用 Async 模块

为了简化异步操作的处理,我们可以使用 async 模块,它提供了多种方法来管理异步操作。

async.waterfall

async.waterfall 方法按顺序执行一系列异步函数,并将前一个函数的结果传递给下一个函数:

var async = require('async');

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); // 输出: 3
    console.log(jj); // 输出: [ 12, 10 ]
});
async.series

async.series 方法并行执行一系列异步函数,但结果会被收集到一个数组中:

async.series([
    function(callback) {
        setTimeout(function() { callback(null, [1, 2]); }, 2000);
    },
    function(callback) {
        setTimeout(function() { callback(null, ['OK', 'hello']); }, 3000);
    }
], function(err, results) {
    console.log(results); // 输出: [[1, 2], ['OK', 'hello']]
});
async.parallel

async.parallel 方法并行执行一系列异步函数,但结果同样被收集到一个数组中:

async.parallel([
    function(callback) {
        setTimeout(function() { callback(null, 'Hello'); }, 1000);
    },
    function(callback) {
        setTimeout(function() { callback(null, 'World'); }, 2000);
    }
], function(err, results) {
    console.log(results); // 输出: ['Hello', 'World']
});
async.auto

async.auto 方法允许你混合顺序和并行执行的异步函数:

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' }
});

通过使用 async 模块,我们可以更好地管理和控制异步操作,避免复杂的递归调用,使代码更加清晰和易于维护。


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

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

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

forEach和each是一样的?

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

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

async 模块提供了强大的异步控制工具,能够有效地管理复杂的异步流程。以下是一些 async 模块中常用的方法及其使用示例。

async.waterfall

async.waterfall 用于按顺序执行一系列异步任务,并将前一个任务的结果传递给下一个任务。

const async = require('async');

async.waterfall([
    function(callback) {
        setTimeout(() => callback(null, 'Step 1'), 1000);
    },
    function(step1Result, callback) {
        console.log(`Step 1 Result: ${step1Result}`);
        setTimeout(() => callback(null, 'Step 2'), 1000);
    },
    function(step2Result, callback) {
        console.log(`Step 2 Result: ${step2Result}`);
        setTimeout(() => callback(null, 'Step 3'), 1000);
    }
], function(err, finalResult) {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log(`Final Result: ${finalResult}`);
    }
});

async.series

async.series 用于并行执行多个异步任务,每个任务的结果会被存储在一个数组中。

const async = require('async');

async.series([
    function(callback) {
        setTimeout(() => callback(null, 'Task 1'), 1000);
    },
    function(callback) {
        setTimeout(() => callback(null, 'Task 2'), 1000);
    }
], function(err, results) {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log(`Results: ${results}`);
    }
});

async.parallel

async.parallel 类似于 async.series,但是所有任务会并行执行,而不是顺序执行。

const async = require('async');

async.parallel([
    function(callback) {
        setTimeout(() => callback(null, 'Task 1'), 1000);
    },
    function(callback) {
        setTimeout(() => callback(null, 'Task 2'), 500);
    }
], function(err, results) {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log(`Results: ${results}`);
    }
});

async.auto

async.auto 允许定义依赖关系,并按需执行任务。

const async = require('async');

async.auto({
    task1: function(callback) {
        setTimeout(() => callback(null, 'Task 1 Result'), 1000);
    },
    task2: function(callback) {
        setTimeout(() => callback(null, 'Task 2 Result'), 500);
    },
    task3: ['task1', 'task2', function(callback, results) {
        console.log(`Results:`, results);
        setTimeout(() => callback(null, `${results.task1} + ${results.task2}`), 1000);
    }]
}, function(err, results) {
    if (err) {
        console.error('Error:', err);
    } else {
        console.log(`Final Results:`, results);
    }
});

通过这些方法,你可以更好地管理和控制复杂的异步流程,避免因异步问题导致的逻辑混乱。

回到顶部