2015-06-06 2 views
7

Я переношу существующую программу на использование async/await (через BabelbluebirdCoroutines), чтобы изучить этот стиль. Я смотрю на это tutorial.ES7 async/await conceptual issue

Я немного обеспокоен следующим поведением. Этот фрагмент кода работает, как ожидалось:

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

Что мне не хватает?

+1

Итерация синхронна, из того, что я понимаю, это произойдет в порядке. Он думает, что «для ..предложение on loop нацелено на улучшение этого, но оно не реализовано нигде afaik. См. Здесь информацию https://github.com/jhusain/asyncgenerator – elclanrs

ответ

2

Функция .map является асинхронной, поэтому остальной части вашего кода не нужно ждать, она будет завершена, когда будет готова. Затем вы заменили его на for loop, который удерживает все, пока он завершается.

+0

Спасибо! Чтение вашего ответа и комментарий @elclanrs заставило меня понять, что на самом деле он сказал эту статью (с другой формулировкой), но мне удалось упустить ее важность: | – rollingBalls

+1

@rollingBalls пользуются! – Datsik

+3

Это не цикл for, который удерживает все обратно - это когда создаются _promises_, что важно - в версии карты вы их создаете заранее, в версии for loop это по одному. Помните, что обещание - это уже начатая операция (загрузка в вашем случае). –

4

Причина, по которой она больше не работает параллельно, связана с тем, когда вы создаете свои обещания в двух примерах. Это более подробно описано above comment.

В вашем первом примере вы начинаете все обещания, которые начинают выполнять свои функции. Тогда в этом цикле:

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

Вы ждете первого обещание быть сделано, а затем второе обещание быть сделано, и т.д. Но все это время вы ждете первого обещают быть сделано все другие обещания все еще выполняются. Следовательно, они работают «параллельно».

В вашем втором примере вы оба НАЧАТЬ и НАЖМИТЕ обещания внутри цикла вместо того, чтобы запускать их перед циклом. Так что в этом коде:

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

parts.push линия выполняет следующие действия по порядку:

  1. пробегов dlPart() который возвращает обещание и начинает загрузку той части
  2. Ждет обещание разрешить
  3. Выдает разрешенное значение в parts.

Все остальные обещания не были запущены и не работают «параллельно», они запускаются только в том случае, если это их очередь в цикле. Это означает, что они вызываются по одному за раз и запускают только следующий запуск, как только предыдущий выполняется, и поэтому они запускаются итеративно.

Примечание: .map is not asynchronous если бы это было, то ваш первый пример не будет работать с большими списками, поскольку цикл for of бы начать, прежде чем все обещания были добавлены в ваш urlsP массив.

1

Вы можете лучше увидеть различия между кодом, когда его написано несколько иначе.

Во всех смыслах и задачах это именно то, что объяснил Сэм, но таким образом, что я нахожу, помогает разработчикам понять это так, как они привыкли.

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 приведены вызовы функций карты всех асинхронных функций и создается новый массив обещаний. for of loop повторяет множество обещаний и проверок, чтобы проверить, разрешено ли обещание, если оно не будет ждать, пока это конкретное обещание не решится, а затем повторите этот процесс. Если бы вы могли наблюдать этот код в замедленном режиме с помощью тега отладчика в хроме, вы заметили бы, что некоторые обещания будут решены к тому времени, когда цикл проверяет, разрешен ли он, в то время как другим вам придется ждать

Пример ES6 по существу тот же, с той лишь разницей, что мы получаем массив деталей. В этом случае Promise весь ответ представляет собой массив из всех разрешенных значений, таким образом, мы делаем равные части к ответу, а не толкая в массив


Теперь представьте себе писать следующий код в 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 пример делает то же самое в том смысле, что она будет начинаться с URL-адрес с индексом 0, ждет dlPart обещание решить, выталкивает ответ в массив частей, checkes, что индекс еще меньше чем длина URLs массива, getParts затем называет себя снова, пока, наконец, не исчерпывает индексов URLS и решает его последнее обещание так, что код ниже Promise.all(promises) может начать работать

Когда вы начинаете смотреть на различия в читаемость между ES6 и ES7 вы можете увидеть, почему async/await были доработаны в es7 s Печ.

Смежные вопросы