事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称“观察者模式”(observer pattern)。
这个模式有多种实现,下面采用的是 Ben Alman 的 Tiny Pub/Sub,这是 jQuery 的一个插件。
jQuery.subscribe('done',f2);
这行的含义是f2在信号中心 订阅 ’done‘ 信号。
function f1() {
setTimeout(function () {
// ...
jQuery.publish('done');
}, 1000);
}
上面代码中,jQuery.publish('done')
的意思是,f1
执行完成后,向信号中心jQuery
发布done
信号,从而引发f2
的执行。
f2
完成执行后,可以取消订阅(unsubscribe)。
jQuery.unsubscribe('done', f2);
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 final(value) {
console.log('完成: ', value);
}
function series(item) {
if(item) {
async( item, function(result) {
results.push(result);
return series(items.shift());
});
} else {
return final(results[results.length - 1]);
}
}
series(items.shift());
上面代码中,函数series
就是串行函数,它会依次执行异步任务,所有任务都完成后,才会执行final
函数。items
数组保存每一个异步任务的参数,results
数组保存每一个异步任务的运行结果。
注意,上面的写法需要六秒,才能完成整个脚本。
流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final
函数。
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 final(value) {
console.log('完成: ', value);
}
items.forEach(function(item) {
async(item, function(result){
results.push(result);
if(results.length === items.length) {
final(results[results.length - 1]);
}
})
});
上面代码中,forEach
方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final
函数。
相比而言,上面的写法只要一秒,就能完成整个脚本。这就是说,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。因此有了第三种流程控制方式。
所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n
个异步任务,这样就避免了过分占用系统资源。
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var limit = 2;
function async(arg, callback) {
console.log('参数为 ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function final(value) {
console.log('完成: ', value);
}
function launcher() {
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) {
final(results);
}
});
running++;
}
}
launcher();
上面代码中,最多只能同时运行两个异步任务。变量running
记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0
,就表示所有任务都执行完了,这时就执行final
函数。
这段代码需要三秒完成整个脚本,处在串行执行和并行执行之间。通过调节limit
变量,达到效率和资源的最佳平衡。