我正在迁移现有程序以使用async
/ await
(通过Babel
的{{1}})来学习此样式。我一直在看这个tutorial。
我对以下行为感到有点困扰。此代码段按预期工作:
bluebirdCoroutines
重写如下,它仍然有效,但第一次操作不再平行(完成需要更长的时间)!
let parts = [];
let urlsP = urls.map((url, index) => {
return dlPart(url, index, tempDir);
});
for (let urlP of urlsP) { // Parallel (yay!)
parts.push(await urlP);
}
for (let part of parts) { // Sequential
await appendFile(leFile, part);
}
这是let index = 0;
let parts = [];
for (let url of urls) { // NOT Parallel any more!!!
index++;
parts.push(await dlPart(url, index, tempDir));
}
for (let part of parts) {
await appendFile(leFile, part);
}
dlPart()
我错过了什么?
答案 0 :(得分:4)
它不再并行运行的原因是因为您在两个示例中创建承诺的时间。这更详细地描述了above comment。
在第一个示例中,您将启动所有开始执行其功能的Promise。然后在这个循环中:
for (let urlP of urlsP) { // Parallel (yay!)
parts.push(await urlP);
}
你等待第一个承诺完成,然后第二个承诺要完成,等等。但是你等待第一个承诺的所有其他承诺仍然在执行。因此他们以“平行”的方式运行。
在你的第二个例子中,你既可以在循环中START和AWAIT,也可以在循环之前启动它们。所以在这段代码中:
for (let url of urls) { // NOT Parallel any more!!!
index++;
parts.push(await dlPart(url, index, tempDir));
}
parts.push
行按顺序执行以下操作:
dlPart()
,它返回一个承诺并开始下载该部分parts
。所有其他的承诺都没有启动,并没有“并行”运行,它们只有在轮到它们时才开始运行。这意味着它们一次被调用一个,只有在前一个完成后才开始执行下一个,这就是迭代运行的原因。
注意:.map
is not asynchronous如果是,那么您的第一个示例将不适用于大型列表,因为for of
循环将在所有承诺添加到您的urlsP
数组之前启动。< / p>
答案 1 :(得分:2)
.map
函数是异步的,因此代码的其余部分不必等待它,它会在准备就绪时完成。然后你用for loop
替换它,它在完成时保留所有内容。
答案 2 :(得分:1)
当以稍微不同的方式编写代码时,您可以更好地看到代码之间的差异。
出于所有意图和目的,这正是Sam所解释的,但我找到的方式可以帮助开发人员以他们更习惯的方式理解它。
ES7代码
let parts = [];
let urlsP = urls.map((url, index) => {
return dlPart(url, index, tempDir);
});
for (let urlP of urlsP) { // Parallel (yay!)
parts.push(await urlP);
}
ES6代码
let parts = [];
// Capture all the pending promises into an array
let urlsP = urls.map((url,index)=>{
// Returns the promise to the urlsP array
return dlPart(url,index,tempDir);
});
// Catch all the data in an array
Promise.all(urlsP).then(res=>{
parts=res;
});
重申Sam对上述帖子的解释。
在 ES7 示例中,map函数调用所有异步函数并创建一个新的promises数组。 for of loop
遍历承诺数组并检查承诺是否已经解决,如果没有,它将等到该特定承诺结算,然后重复此过程。如果您能够使用chrome中的调试器标签以慢动作观看此代码,您会注意到某些承诺将在循环检查以确定是否已解决时解决,而其他承诺将等待
ES6 示例基本相同,唯一不同的是我们获取零件数组的方式。在这种情况下,Promise all响应是所有已解析值的数组,因此我们使部件等于响应而不是推入数组
现在想象一下在es6中编写以下代码:
ES7代码
let index = 0; let parts = [];
for (let url of urls) { // NOT Parallel any more!!!
index++;
parts.push(await dlPart(url, index, tempDir));
}
你必须使用生成器或递归函数,我对生成器的理解仍然很新,所以我将展示递归函数
ES5 / 6代码
let index = 0; let parts = []; let promises = [];
function getParts(index,){
return new Promise((resolve,reject)=>{
dlPart(urls[index],index,tempDir).then(res=>{
parts.push(res)
if(index<urls.length-1){
promises.push(getParts(++index));
resolve()
}else{
resolve()
}
}
}
}
promises.push(getParts(index));
Promise.all(promises).then(finished=>{
// Then you can continue with whatever code
});
现在使用上面的 ES7 示例代码,您将注意到for of loop
遍历urls数组并等待承诺解析,然后转移到下一个索引阵列。
ES6 示例的含义相同,它将以索引0处的url开头,等待dlPart
承诺解析,将响应推送到parts数组,检查索引是否仍然小于urls数组长度,然后getParts再次调用自己,直到最后它用完了urls索引并解析了它的最后一个promise,以便代码低于{{1可能会开始运行
当您开始研究ES6和ES7之间可读性的差异时,您可以看到为什么async / await在es7规范中最终确定。