7

我正在迁移现有程序以使用async/ await(通过Babel's 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()

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

我错过了什么?

4

3 回答 3

4

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

在您的第一个示例中,您启动了所有开始执行其功能的 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如果不是异步的,那么您的第一个示例将不适用于大型列表,因为for of循环将在所有承诺添加到您的urlsP数组之前开始。

于 2015-10-15T21:21:53.390 回答
2

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

于 2015-06-06T00:12:42.963 回答
1

You can better see the differences between the code when its written in a slightly different way.

For all intents and purposes this is exactly what Sam has explained, but in a way that I find helps developers understand it in a way that they are more accustomed to.

ES7 Code

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 Code

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

To reiterate what Sam has explained the post above. In the ES7 example the map function calls of all the async functions and creates a new array of promises. The for of loop iterates through the array of promises and checks to see if the promise has been resolved yet, if it hasn't it will wait until the that specific promise resolves, then repeat this process. If you were able to watch this code in slow motion with a debugger tag in chrome you would notice that some promises will be resolved by the time the loop checks to see if it was resolved, while others you will have to wait for

The ES6 example is essentially the same, with the only difference in how we get the parts array. In this case the Promise all response is an array of all the resolved values, thus we make parts equal to the response instead of pushing into the array


Now imagine writing the following code in es6:

ES7 Code

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

You would have to use a generator or a recursive function, My understanding of generators is still pretty new so I will show a recursive function

ES5/6 Code

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

Now with this ES7 example code above you will notice that the for of loop iterates through the urls array and waits for the promise to resolve before moving to the next index of the array.

The ES6 example does the same in the sense that it will begin with the url at index 0, waits for dlPart promise to resolve, pushes the response into the parts array, checkes that the index is still smaller than the urls array length, getParts then calls itself again, until finally it runs out of urls indices and resolves its last promise so that the code below the Promise.all(promises) may begin to run

When you begin looking at the differences in the readability between ES6 and ES7 you can see why async/await were finalized in the es7 spec.

于 2017-08-03T22:54:08.690 回答