场景:使用工具函数downloadAllAsync接收一个URL数组并下载所有文件,结果返回一个存储了文件内容的数组,每个URL对应一个字符串。
好处:downloadAllAsync并不只有清理嵌套回调函数的好处,其主要好处是并行下载文件。我们可以在同一个事件循环中一次启动所有文件的下载,而不用等待每个文件完成下载。
并行逻辑是微妙的,很容易出错。下面的实现有一个隐蔽的缺陷。
function downloadAllAsync(urls, onsuccess, onerror) {
var result = [],
len = urls.length;
if(len === 0) { // 如果请求路径为空, 不执行下面的程序
// 绝不要同步地调用异步的回调函数
setTimeout(onsuccess.bind(null, result), 0);
return;
}
urls.foreach(function(url) {
downloadAsync(url, function(r) {
if(result) {
result.push(r); // race condition
// 根据提供的url, 所有文件数据被成功下载后,执行onsuccess程序
result.length === len && onsuccess(result);
}
}, function(e) {
if (result) {
result = null; // 在错误的情况下, 确保onerror只执行一次
onerror(e);
}
})
})
}
如果有多个下载失败,我们设置了result数组为null,从而保证onerror只被调用一次。即在第一次错误发生时。
downloadAllAsync函数实现的是一旦下载完成就立即将中间结果保存在result数组的末尾。因此,陷阱是保存下载文件内容的数组的顺序是未知的。几乎不能正确使用这样的API,因为调用者无法找出哪个结果对应哪个文件。
疑问:为什么使用setTimeout函数来调用onsuccess回调函数,而不是直接调用它
我们存储在原始的索引位置来提供预期结果
function downloadAllAsync(urls, onsuccess, onerror) {
var result = [],
len = urls.length;
if(len === 0) {
setTimeout(onsuccess.bind(null, result), 0);
return;
}
urls.foreach(function(url, index) {
downloadAsync(url, function(r) {
if(result) {
result[index] = r; // store at fixed index
result.length === len && onsuccess(result); // race condition
}
}, function(e) {
if (result) {
result = null;
onerror(e);
}
})
})
}
该实现利用了foreach回到函数的第二个参数。该参数为当前迭代提供的数组索引。不幸的是,这仍然不正确。
数组更新契约,即设置一个索引属性,总是确保数组的length属性大于索引。
正确的实现应用了一个计数器来追踪正在进行的操作数量。
function downloadAllAsync(urls, onsuccess, onerror) {
var result = [],
pending = urls.length;
if(pending === 0) {
setTimeout(onsuccess.bind(null, result), 0);
return;
}
urls.foreach(function(url, index) {
downloadAsync(url, function(r) {
if(result) {
result[index] = r; // store at fixed index
// pending -= 1; // register the success
// pending === 0 && onsuccess(result); // race condition
--padding || onsuccess(result);
}
}, function(e) {
if (result) {
result = null;
onerror(e);
}
})
})
}
现在整个世界都太平了,不论事情以什么样的顺序发生,pending计数器都能准确地指出何时所有的事件会被完成,并以预期的顺序返回完整的结果。
参考:编写高质量JS代码68个有效方法
原文:http://www.cnblogs.com/mackxu/p/downloadAllAsync.html