ES7asynchronous/等待概念问题

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

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

 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函数是asynchronous的,所以你的代码的其余部分不需要等待,它会在准备好的时候完成。 然后你用一个for loop代替它,它在完成时把所有东西都保存起来。

它不再是平行的原因是因为你在两个例子中创造你的承诺的时机。 这在上面更加清楚地说明了。

在你的第一个例子中,你启动了所有开始执行其function的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. 将parsing后的值分解成若干parts

所有其他的承诺还没有开始,并没有“平行”运行,他们只是在轮到他们时才开始。 这意味着他们一次只能调用一个,而且只有在前一个完成时才启动下一个,这就是迭代运行的原因。

注意: .map 不是asynchronous的 ,那么你的第一个例子不能用于大型列表,因为for of循环会在所有的promise被添加到你的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函数调用所有的asynchronous函数,并创build一个新的promise数组。 for of loop遍历promise数组并检查promise是否已经被parsing,如果没有,它会等待那个特定的promise解决,然后重复这个过程。 如果你可以用慢速运动来看这个代码,用chrome中的debugging器标签,你会注意到一些promise会在循环检查时被解决,看看它是否被解决了,而其他的你将不得不等待

ES6的例子基本上是一样的,唯一的区别就是我们如何得到零件数组。 在这种情况下,Promise all response是所有parsing值的数组,因此我们使得部分与响应相等而不是推入数组


现在设想在es6中编写下面的代码:

ES7代码

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

你将不得不使用一个生成器或recursion函数,我对生成器的理解还是相当新的,所以我将展示一个recursion函数

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的例子也是一样的,它将以index 0处的url开始,等待dlPart承诺parsing,将响应推入到parts数组中,检查索引是否仍然小于urls数组的长度,然后getParts再次调用自己,直到最后它耗尽urls索引并解决它的最后一个承诺,以便Promise.all(promises)下面的代码可能开始运行

当您开始查看ES6和ES7之间可读性的区别时,您可以看到为什么asynchronous/等待在es7规范中定稿。