如果有多个异步操作,就会存在一个流程控制的问题,如何保证代码按照自己想要的流程执行下去。
例如:下面是一个异步任务,非常耗时,每次需要1秒才能完成,然后再调用回调函数。
function async(arg, callBack) {
console.log('参数为:' + arg +' , 1秒后返回结果');
setTimeout(function () { callBack(arg * 2); }, 1000);
}
如果有五个这样的异步任务,需要全部执行完成后才能执行任务 end 函数,怎么安排?
function end(value) {
console.log('完成: ', value);
}
async(1, function (value) {
async(2, function (value) {
async(3, function (value) {
async(4, function (value) {
async(5, final);
});
});
});
});
// 参数为 1 , 1秒后返回结果
// 参数为 2 , 1秒后返回结果
// 参数为 3 , 1秒后返回结果
// 参数为 4 , 1秒后返回结果
// 参数为 5 , 1秒后返回结果
// 完成: 10
正常同步写法为嵌套,完成全部任务需要5秒的时间。缺点:容易出错,代码可阅读性差。
串联执行
我们可以写一个流程控制函数,让它来控制异步任务,一个任务完成后再执行下一个任务,这叫做串联执行。
ar items = [ 1, 2, 3, 4, 5];
var results = [];
function async(arg, callback) {
console.log('参数为: ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function end(value) {
console.log('完成: ', value);
}
function series(item) {
if(item) {
async( item, function(result) {
results.push(result);
return series(items.shift());
});
} else {
return end(results[results.length - 1]);
}
}
series(items.shift());
series函数就是串联执行函数,它会依次执行async异步任务,所有异步任务完成后才会执行end函数。items保存每一个异步任务的参数,results保存异步任务完成后的结果。缺点:整个异步任务执行完成需要5秒。
并联执行
我们可以修改一下代码,让异步任务并联执行,这样所有的异步任务可以同时执行,当所以异步任务执行完成后再执行end函数,以下代码只需一秒便可执行end函数:
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
function async(arg, callback) {
console.log('参数为: ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function end(value) {
console.log('完成: ', value);
}
items.forEach(function(item) {
async(item, function(result){
results.push(result);
if(results.length === items.length) {
end(results[results.length - 1]);
}
})
});
缺点:同时执行多个异步任务,有可能消耗大量性能,拖慢运行速度。如果我们能控制每次执行异步任务的数量,做到并联执行与串联执行的平衡,那岂不是更好?
并联与串联的结合
并联与串联执行结合的意思就是设置一个条件,每次只能n个异步请求,这样就能控制异步任务对系统资源的消耗。
var items = [ 1, 2, 3, 4, 5];
var results = [];
var running = 0;
var limit = 2;
function async(arg, callback) {
console.log('参数为: ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function end(value) {
console.log('完成: ', value);
}
function go() {
while(running < limit && items.length > 0) {
var item = items.shift();
async(item, function(result) {
results.push(result);
running--;
if(items.length > 0) {
launcher();
} else if(running == 0) {
end(results);
}
});
running++;
}
}
go();
上面的go函数中,最多同时运行两个异步任务,变量running记录正在执行的任务数量,当小于条件(这里条件为:running<2)时启动下一个异步任务,如果running等于0,表示所有异步任务都执行完成,最后执行end函数。
这段代码每次同时进行2个异步任务,通过limit变量调节每次执行异步任务的数量,做到了串联与并联、效率与资源的平衡 。
原文引自阮一峰老师:异步操作概述