ES7 async/await 概念问题

ES7 async/await conceptual issue

我正在迁移现有程序以使用 async/await(通过 BabelbluebirdCoroutines)以学习这种风格。我一直在看这个 tutorial.

我对以下行为有点困扰。此代码段按预期工作:

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()

的实现
function dlPart(url, num, dir) {
    var cmd = 'wget --quiet "' + url + '" -O ' + dir + "/" + num;
    return exec(cmd).then(() => {
        return dir + '/' + num;
    });
}

我错过了什么?

.map 函数是异步的,因此您的其余代码不必等待它,它会在准备就绪时完成。然后你用 for loop 替换它,它在完成时阻止所有内容。

它不再 运行 并行的原因是因为您在两个示例中创建承诺的时间。这个描述得更清楚

在您的第一个示例中,您启动了所有开始执行其功能的承诺。然后在这个循环中:

for (let urlP of urlsP) { // Parallel (yay!)
    parts.push(await urlP);
}

您等待第一个 promise 完成,然后第二个 promise 完成,等等。但是在您等待第一个 promise 完成的整个过程中,所有其他 promise 仍在执行。因此他们 运行 在 "parallel".

在您的第二个示例中,您在循环内启动和等待承诺,而不是在循环之前启动它们。所以在这段代码中:

for (let url of urls) { // NOT Parallel any more!!!
    index++;
    parts.push(await dlPart(url, index, tempDir));
}

parts.push 行按顺序执行以下操作:

  1. 运行 dlPart() returns 承诺并开始下载部分
  2. 等待承诺解决
  3. 将解析值推入 parts

所有其他 promise 还没有开始,也没有 运行ning "in parallel" 它们只有在轮到他们进入循环时才开始。这意味着他们一次被调用一个,只有在前一个完成后才开始执行下一个,这就是为什么他们 运行 迭代。

注意:.map is not asynchronous 如果是,那么您的第一个示例将不适用于大型列表,因为 for of 循环将在所有承诺添加到您的 urlsP 数组之前开始.

当代码以稍微不同的方式编写时,您可以更好地看到代码之间的差异。

出于所有意图和目的,这正是 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 在上面 post 中的解释。 在 ES7 示例中,map 函数调用所有异步函数并创建一个新的承诺数组。 for of loop 遍历承诺数组并检查承诺是否已解决,如果尚未解决,它将等到特定承诺解决,然后重复此过程。如果您能够使用 chrome 中的调试器标签以慢动作观看此代码,您会注意到某些承诺将在循环检查它是否已解决时得到解决,而其他承诺则必须等待对于

ES6 示例本质上是相同的,唯一的区别在于我们如何获取 parts 数组。在这种情况下,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 promise to resolve,将响应推送到 parts 数组,检查索引是否仍然小于 urls 数组长度,然后 getParts 再次调用自身,直到最后 运行s out of urls 索引并解决其最后的承诺,以便 Promise.all(promises) 下面的代码可以开始 运行

当您开始查看 ES6 和 ES7 之间的可读性差异时,您会明白为什么 async/await 在 es7 规范中最终确定。