【问题标题】:ES7 async/await conceptual issueES7 async/await 概念问题
【发布时间】:2015-08-21 01:14:23
【问题描述】:

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

我对以下行为有点困扰。这个 sn-p 按预期工作:

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;
    });
}

我错过了什么?

【问题讨论】:

  • 迭代是同步的,据我了解,它会按顺序发生。它认为for..on 循环提案旨在改善这一点,但它并没有在任何地方实施。请参阅此处了解信息github.com/jhusain/asyncgenerator

标签: javascript node.js bluebird ecmascript-2016


【解决方案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;
});

重申山姆对上述帖子的解释。 在 ES7 示例中,map 函数调用所有异步函数并创建一个新的 Promise 数组。 for of loop 遍历promise 数组并检查promise 是否已解决,如果尚未解决,它将等到特定promise 解决,然后重复此过程。如果您能够使用 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 数组并等待 promise 解决,然后再移动到数组的下一个索引。

ES6 示例的作用相同,它将从索引 0 处的 url 开始,等待 dlPart 承诺解决,将响应推送到部分数组,检查索引是否仍然小于 urls 数组长度,然后 getParts 再次调用自己,直到最后它用完 urls 索引并解析它的最后一个承诺,以便Promise.all(promises) 下面的代码可以开始运行

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

【讨论】:

    【解决方案2】:

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

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

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

    你等待第一个承诺完成,然后第二个承诺完成,等等。但在你等待第一个承诺完成的整个过程中,所有其他承诺仍在执行。因此它们“并行”运行。

    在您的第二个示例中,您既 START 又 AWAIT 循环内的 Promise,而不是在循环之前启动它们。所以在这段代码中:

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

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

    1. 运行dlPart(),它返回一个承诺并开始下载该部分
    2. 等待承诺解决
    3. 将解析后的值推送到parts

    所有其他 Promise 都没有启动,也没有“并行”运行,它们只有在轮到他们循环时才会启动。这意味着它们一次调用一个,只有在前一个完成后才开始执行下一个,这就是它们迭代运行的原因。

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 谢谢!阅读您的回答和@elclanrs 评论让我意识到这篇文章实际上是这么说的(用另一种措辞),但我设法错过了它的重要性:|
      • 阻止一切的不是 for 循环 - 重要的是 promise 的创建时间 - 在 map 版本中,您提前创建它们,在 for 循环版本中它是一一的。请记住,promise 是一个已经开始的操作(在你的情况下下载)。
      猜你喜欢
      • 1970-01-01
      • 2020-03-25
      • 2017-03-27
      • 2011-10-22
      • 1970-01-01
      • 1970-01-01
      • 2017-01-04
      • 2011-03-11
      • 1970-01-01
      相关资源
      最近更新 更多