假设要编写一些异步的代码:
- 打开路径的句柄
- 判断路径是否指向一个文件
- 如果路径指向一个文件,加载这个文件的内容
- 关闭句柄并将内容返回给调用者
函数如下所示:
var fs = require("fs");
function load_file_contents(path, callback) {
fs.open(path, 'r', function(err, file) {
if (err) {
callback(err);
return;
} else if (!file) {
callback({error: "无效句柄", message: "fs.open未能正确打开文件!"});
return;
}
fs.fstat(file, function (err, stats) {
if (err) {
callback(err);
return;
}
if (stats.isFile()) {
var buf = new Buffer(10000);
fs.read(file, buf, 0, 10000, null, function (err, br, buf) {
if (err) {
callback(err);
return;
}
fs.close(file, function (err) {
if (err) {
callback(err);
return;
}
callback(null, buf.toString('utf8', 0, br));
})
})
} else {
callback({error: "不是文件", message: "无法加载文件夹"});
return;
}
})
});
}
//加载D盘下的info.txt文件
load_file_contents('d:/info.txt', function (err, contents) {
if (err) {
console.log(err);
return;
}
console.log(contents);
})
不难发现,以上代码从一开始就深度嵌套。当嵌套层度超过一定程度时,代码的可读性就降低了。
要解决这个问题,可以使用async模块。
串行执行
以串行方式执行异步代码的方式有两种:waterfall函数和series函数。
waterfall函数接收一个函数数组作为参数,然后把每一个函数的结果传给下一个函数。结束时,结果函数会接收函数数组中最后一个函数的返回结果作为参数并执行。这种方式下,任何一步出错,执行都会停止。
现在用async.waterfall重写之前的代码:
var fs = require('fs');
var async = require('async');
function load_file_contents(path, callback) {
async.waterfall([
function (callback) {
console.log("执行第1步");
fs.open(path, 'r', callback);
},
function (file, callback) {
console.log("执行第2步");
fs.fstat(file, function (err, stats) {
if (err) {
callback(err);
} else {
callback(null, file, stats);
}
});
},
function (file, stats, callback) {
console.log("执行第3步");
if (stats.isFile()) {
var buf = new Buffer(10000);
fs.read(file, buf, 0, 10000, null, function (err, br, buf) {
if (err) {
callback(err);
return;
}
callback(null, file, buf.toString('utf8', 0, br));
});
} else {
callback({error: "不是文件", message: "无法加载这个文件夹"});
}
},
function (file, contents, callback) {
console.log("执行第4步");
fs.close(file, function (err) {
if (err) {
callback(err);
} else {
callback(null, contents);
}
})
}
]);
}
//加载info.txt
load_file_contents('d:/info.txt', function (err, contents) {
if (err) {
console.log("出错!");
return;
}
console.log(contents);
})
运行结果:
执行第1步
执行第2步
执行第3步
执行第4步
------------------
结果已经返回:
Hello World!
你好,世界!
async.series函数和async.waterfall函数有两个区别:
- 来自一个函数的结果不是传给下一个函数,而是收集到一个数组中,这个数组作为参数传给最后的结果函数。依次调用的每一个函数都会称为这个数组中的一个元素
- 可以给async.series传递一个对象,它会枚举每一个key并执行每个key对应的函数。这时候,结果就不是作为一个数组传入,而是对象
区别1:
var async = require('async');
function testAsyncSeries(callback) {
async.series([
function (callback) {
callback(null, [1, 2, 3]);
},
function (callback) {
callback(null, ['a', 'b', 'c']);
}],
function (err, results) {
callback(null, results);
}
);
}
testAsyncSeries(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
运行结果:
[ [ 1, 2, 3 ], [ 'a', 'b', 'c' ] ]
区别2:
var async = require('async');
function testAsyncSeries(callback) {
async.series({
numbers: function (callback) {
callback(null, [1, 2, 3]);
},
strings: function (callback) {
callback(null, ['a', 'b', 'c']);
}
},
function (err, results) {
callback(null, results);
}
);
}
testAsyncSeries(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
运行结果:
{ numbers: [ 1, 2, 3 ], strings: [ 'a', 'b', 'c' ] }
并行执行
在async.series的例子,函数没有理由串行执行:第二个函数并不依赖第一个函数的结果。应当以并行的方式执行它们:
var async = require('async');
function testAsyncParallel(callback) {
async.parallel({
numbers : function (callback) {
callback(null,[1, 2, 3]);
},
strings : function (callback) {
callback(null,['a', 'b', 'c']);
}
},
function (err, results) {
return callback(null, results);
}
);
}
testAsyncParallel(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
运行结果:
{ numbers: [ 1, 2, 3 ], strings: [ 'a', 'b', 'c' ] }
串行和并行结合起来使用*
async模块提供了函数auto,能够将顺序执行的函数和非顺序执行的函数混合起来称为一个函数序列。使用这个函数,应传入一个对象,它的key包含了:
- 将要执行的函数,或者
- 一个依赖数组和一个将要执行的函数。这些依赖都是字符串,是提供给async.auto的对象的属性名。auto函数会等待这些依赖都执行完才会调用我们提供的函数。
举个例子:
var async = require('async');
function testAsyncAuto(callback) {
async.auto({
numbers: function (callback) {
console.log("调用numbers");
callback(null, [1, 2, 3]);
},
strings: function (callback) {
console.log("调用strings");
callback(null, ['a', 'b', 'c']);
},
assemble: [
'numbers', 'strings',
function (results, callback) {
callback(null,{numbers: results.numbers, strings: results.strings});
}
]
}, function (err, results) {
callback(null, results);
});
}
testAsyncAuto(function (err, results) {
if (err) {
console.log(err);
return;
}
console.log(results);
});
运行结果:
{ numbers: [ 1, 2, 3 ],
strings: [ 'a', 'b', 'c' ],
assemble: { numbers: [ 1, 2, 3 ], strings: [ 'a', 'b', 'c' ] } }
异步循环
可以使用async.forEachSeries来遍历数组中的项:
var async = require('async');
var a = [1, 2, 3, 4, 9];
function testAsyncEachSeries(a, callback) {
async.forEachSeries(
a, //要遍历的数组
function(element, callback) {
console.log(element);//对数组元素进行的操作,我这里只是简单打印
callback(null);//YOU MUST CALL ME FOR EACH ELEMENT!
},
// called at the end
function (err) {
callback(err);//没出错的话,err will be non-null then
}
);
}
testAsyncEachSeries(a, function (err) {
if (err) {
return;
}
});
运行结果:
1
2
3
4
9
可以看到数组中的每一个元素都会以相同的方式被调用,但是呢它不会串行地执行函数。