ES7异步/等待概念问题

时间:2015-06-06 00:01:42

标签: javascript node.js bluebird ecmascript-7

我正在迁移现有程序以使用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()

我错过了什么?

3 个答案:

答案 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行按顺序执行以下操作:

  1. 运行dlPart(),它返回一个承诺并开始下载该部分
  2. 等待承诺解决
  3. 将已解决的值推送到parts
  4. 所有其他的承诺都没有启动,并没有“并行”运行,它们只有在轮到它们时才开始运行。这意味着它们一次被调用一个,只有在前一个完成后才开始执行下一个,这就是迭代运行的原因。

    注意:.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规范中最终确定。