Есть ли проблемы с использованием async
/ await
в forEach
цикле? Я пытаюсь перебрать массив файлов и await
содержимое каждого файла.
import fs from 'fs-promise'
async function printFiles () {
const files = await getFilePaths() // Assume this works fine
files.forEach(async (file) => {
const contents = await fs.readFile(file, 'utf8')
console.log(contents)
})
}
printFiles()
Этот код работает, но что-то может пойти не так? Кто-то сказал мне, что вы не должны использовать async
/ await
в функции более высокого порядка, как это, поэтому я просто хотел спросить, есть ли какие-либо проблемы с этим.
for ... of ...
работает?async
/await
в функцию генератора, а использованиеforEach
означает, что каждая итерация имеет отдельную функцию генератора, которая не имеет ничего общего с другими. поэтому они будут выполняться независимо друг от друга и не имеют контекстаnext()
с другими. На самом деле, простойfor()
цикл также работает, потому что итерации также находятся в одной функции генератора.await
приостанавливает текущую оценку функции , включая все управляющие структуры. Да, это очень похоже на генераторы в этом отношении (именно поэтому они используются для полизаполнения async / await).async
функция сильно отличается отPromise
обратного вызова исполнителя, но да,map
обратный вызов возвращает обещание в обоих случаях.С ES2018 вы можете значительно упростить все вышеперечисленные ответы на:
Смотрите спецификацию: предложение-асинхронная итерация
2018-09-10: Этот ответ в последнее время привлекает большое внимание, пожалуйста, см. Сообщение в блоге Акселя Раушмайера для получения дополнительной информации об асинхронной итерации: ES2018: асинхронная итерация
источник
of
должна быть асинхронная функция, которая будет возвращать массив. Это не работает, и Франциско сказал;Вместо того, чтобы
Promise.all
в сочетании сArray.prototype.map
(который не гарантирует порядок, в которомPromise
разрешены s), я используюArray.prototype.reduce
, начиная с разрешенногоPromise
:источник
Promise.resolve()
иawait promise;
?Promise.resolve()
возвращает уже разрешенныйPromise
объект, поэтомуreduce
онPromise
должен начинаться с.await promise;
будет ждать решения последнейPromise
в цепочке. @GollyJer Файлы будут обрабатываться последовательно, по одному за раз.Модуль p-итерации в npm реализует методы итераций Array, поэтому их можно очень просто использовать с async / await.
Пример с вашим делом:
источник
some
а неforEach
. Спасибо!Вот несколько
forEachAsync
прототипов. Обратите внимание, что вам нужноawait
:Обратите внимание: хотя вы можете включать это в свой собственный код, вы не должны включать это в библиотеки, которые вы распространяете среди других (чтобы не загрязнять их глобальные переменные).
источник
_forEachAsync
), это разумно. Я также думаю, что это самый хороший ответ, поскольку он экономит много стандартного кода.globals.js
было бы хорошо), мы можем добавлять глобалы по своему усмотрению.В дополнение к ответу @ Bergi , я хотел бы предложить третий вариант. Это очень похоже на второй пример @ Bergi, но вместо того, чтобы ждать каждого по
readFile
отдельности, вы создаете массив обещаний, каждое из которых вы ожидаете в конце.Обратите внимание, что переданная функция
.map()
не обязательнаasync
, посколькуfs.readFile
в любом случае возвращает объект Promise. Следовательноpromises
, это массив объектов Promise, которые можно отправитьPromise.all()
.В ответе @ Bergi консоль может записывать содержимое файла в порядке их чтения. Например, если действительно маленький файл заканчивает чтение перед действительно большим файлом, он сначала регистрируется, даже если маленький файл идет после большого файла в
files
массиве. Тем не менее, в моем методе выше, вы гарантированно, консоль будет записывать файлы в том же порядке, что и предоставленный массив.источник
await Promise.all
), но файлы могли быть прочитаны в другом порядке, что противоречит вашему утверждению: «вы гарантированно, что консоль будет записывать файлы в том же порядке, в каком они есть». читать".Решение Берги прекрасно работает, когда
fs
оно основано на обещаниях. Вы можете использоватьbluebird
,fs-extra
илиfs-promise
для этого.Тем не менее, решение для собственной
fs
библиотеки узла выглядит следующим образом:Примечание:
require('fs')
принудительно принимает функцию в качестве 3-го аргумента, в противном случае выдает ошибку:источник
Оба вышеупомянутых решения работают, однако, Антонио выполняет работу с меньшим количеством кода, вот как это помогло мне разрешить данные из моей базы данных, от нескольких разных дочерних ссылок, а затем поместить их все в массив и разрешить их в обещании, в конце концов, сделанный:
источник
довольно просто вставить пару методов в файл, который будет обрабатывать асинхронные данные в последовательном порядке и придавать вашему коду более привычный вид. Например:
теперь, предполагая, что это сохранено в «./myAsync.js», вы можете сделать что-то похожее на приведенное ниже в соседнем файле:
источник
Как ответ @ Берги, но с одним отличием.
Promise.all
отвергает все обещания, если кто-то получает отказ.Итак, используйте рекурсию.
PS
readFilesQueue
ЭтоprintFiles
побочный эффект *, введенныйconsole.log
, лучше издеваться, тестировать и / или шпионить, поэтому не круто иметь функцию, которая возвращает контент (sidenote).Следовательно, код можно просто спроектировать следующим образом: три отдельные функции, которые являются «чистыми» ** и не вызывают побочных эффектов, обрабатывают весь список и могут быть легко изменены для обработки неудачных случаев.
Будущее редактирование / текущее состояние
Node поддерживает ожидание верхнего уровня (у него еще нет плагина, он не будет и может быть включен с помощью флагов гармонии), это круто, но не решает одну проблему (стратегически я работаю только на версиях LTS). Как получить файлы?
Используя композицию. Учитывая код, у меня возникает ощущение, что это внутри модуля, поэтому должна быть функция для этого. Если нет, вам следует использовать IIFE, чтобы обернуть код роли в асинхронную функцию, создающую простой модуль, который сделает все для вас, или вы можете пойти по правильному пути, если есть, композиция.
Обратите внимание, что имя переменной изменяется из-за семантики. Вы передаете функтор (функция, которая может быть вызвана другой функцией) и получаете указатель на память, которая содержит начальный блок логики приложения.
Но если это не модуль и вам нужно экспортировать логику?
Оберните функции в асинхронную функцию.
Или измените имена переменных, как угодно ...
*
под побочным эффектом подразумевается любой побочный эффект приложения, который может изменить состояние или поведение или вызвать ошибки в приложении, например, IO.**
«чистый» - это апостроф, поскольку функции не чистые, и код можно конвертировать в чистую версию, когда нет вывода на консоль, только манипуляции с данными.Помимо этого, чтобы быть чистым, вам нужно работать с монадами, которые обрабатывают побочный эффект, которые подвержены ошибкам, и обрабатывают эту ошибку отдельно от приложения.
источник
Одно важное предостережение :
await + for .. of
метод иforEach + async
способ на самом деле имеют разный эффект.Наличие
await
внутри реальногоfor
цикла обеспечит выполнение всех асинхронных вызовов один за другим. И этотforEach + async
способ будет запускать все обещания одновременно, что быстрее, но иногда перегружено ( если вы выполняете какой-либо запрос к БД или посещаете некоторые веб-службы с ограничениями по объему и не хотите запускать 100 000 вызовов одновременно).Вы также можете использовать
reduce + promise
(менее элегантно), если вы не используетеasync/await
и хотите убедиться, что файлы читаются один за другим .Или вы можете создать forEachAsync, чтобы помочь, но в основном используйте то же самое для цикла.
источник
forEach
- доступ к индексам вместо того, чтобы полагаться на итеративность, - и передать индекс в обратный вызов.Array.prototype.reduce
способ, который использует асинхронную функцию. Я показал пример в своем ответе: stackoverflow.com/a/49499491/2537258Используя Task, Futurize и просматриваемый список, вы можете просто сделать
Вот как вы это настроите
Другой способ структурировать нужный код будет
Или, возможно, еще более функционально ориентированный
Тогда из родительской функции
Если вам действительно нужна большая гибкость в кодировании, вы можете просто сделать это (для забавы, я использую предложенный оператор Pipe Forward )
PS - я не пробовал этот код на консоли, возможно, есть некоторые опечатки ... "прямой фристайл, с верхней части купола!" как сказали бы дети 90-х. :-п
источник
В настоящее время свойство прототипа Array.forEach не поддерживает асинхронные операции, но мы можем создать наше собственное poly-fill для удовлетворения наших потребностей.
И это все! Теперь у вас есть асинхронный метод forEach, доступный для любых массивов, определенных после этих операций.
Давайте проверим это ...
Мы могли бы сделать то же самое для некоторых других функций массива, таких как map ...
... и так далее :)
Некоторые вещи, на которые стоит обратить внимание:
Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
будут иметь эту функцию доступнойисточник
Просто добавив к исходному ответу
источник
Чтобы увидеть, как это может пойти не так, напечатайте console.log в конце метода.
Вещи, которые могут пойти не так в целом:
Это не всегда неправильно, но часто в стандартных случаях использования.
Как правило, использование forEach приведет ко всему, кроме последнего. Он будет вызывать каждую функцию, не ожидая функции, то есть он сообщает всем функциям о запуске, а затем о завершении, не дожидаясь завершения функций.
Это пример в нативном JS, который будет сохранять порядок, предотвращать преждевременный возврат функции и теоретически сохранять оптимальную производительность.
Это будет:
С этим решением первый файл будет показан, как только он станет доступен, без необходимости ждать, пока другие будут доступны в первую очередь.
Он также будет загружать все файлы одновременно, вместо того, чтобы ждать окончания первого, прежде чем можно будет начать чтение второго файла.
Единственный недостаток этого и оригинальной версии заключается в том, что если запускать несколько операций чтения одновременно, то более трудно обрабатывать ошибки из-за наличия большего количества ошибок, которые могут происходить одновременно.
С версиями, которые читают файл за раз, затем останавливается на сбое, не тратя время на попытки прочитать больше файлов. Даже при сложной системе отмены может быть сложно избежать сбоя первого файла, но уже можно прочитать и большинство других файлов.
Производительность не всегда предсказуема. Хотя многие системы будут работать быстрее с параллельным чтением файлов, некоторые предпочтут последовательное чтение. Некоторые из них являются динамическими и могут смещаться под нагрузкой. Оптимизация, обеспечивающая задержку, не всегда дает хорошую пропускную способность в условиях сильной конкуренции.
В этом примере также нет обработки ошибок. Если что-то требует, чтобы они либо были успешно показаны, либо вовсе не показаны, это не будет сделано.
На каждом этапе рекомендуется углубленное экспериментирование с console.log и поддельными решениями для чтения файлов (вместо этого случайная задержка). Хотя многие решения, кажется, делают то же самое в простых случаях, у всех есть тонкие различия, которые требуют некоторого дополнительного изучения, чтобы выжать.
Используйте этот макет, чтобы понять разницу между решениями:
источник
Сегодня я столкнулся с несколькими решениями для этого. Запуск асинхронных функций ожидания в цикле forEach. Создавая оболочку вокруг, мы можем сделать это.
Более подробное объяснение того, как это работает внутренне, для нативного forEach и почему оно не может выполнить асинхронный вызов функции, и другие подробности о различных методах приведены в ссылке здесь
Несколько способов, которыми это может быть сделано, и они заключаются в следующем,
Способ 1: использование обертки.
Способ 2: Использование так же, как универсальная функция Array.prototype
Array.prototype.forEachAsync.js
Применение :
Способ 3:
Использование Promise.all
Метод 4: Традиционный для цикла или современный для цикла
источник
Promise.all
следовало бы использовать - они не учитывают ни один из многих крайних случаев.Promise.all
.Promise.all
это невозможно, ноasync
/await
есть. И нет,forEach
абсолютно не обрабатывает никаких обещаний ошибок.Это решение также оптимизировано для памяти, поэтому вы можете запустить его для 10 000 элементов данных и запросов. Некоторые из других решений приведут к сбою сервера на больших наборах данных.
В TypeScript:
Как пользоваться?
источник
Вы можете использовать
Array.prototype.forEach
, но async / await не так совместимо. Это связано с тем, что обещание, возвращаемое из асинхронного обратного вызова, ожидает разрешения, ноArray.prototype.forEach
не разрешает никаких обещаний при выполнении своего обратного вызова. Итак, вы можете использовать forEach, но вам придется самостоятельно обрабатывать обещание.Вот способ прочитать и распечатать каждый файл последовательно, используя
Array.prototype.forEach
Вот способ (все еще используется
Array.prototype.forEach
) для печати содержимого файлов параллельноисточник
Как и у Антонио Вала
p-iteration
, альтернативный модуль npmasync-af
:Кроме того,
async-af
есть статический метод (log / logAF), который регистрирует результаты обещаний:Тем не менее, основным преимуществом библиотеки является то, что вы можете связать асинхронные методы, чтобы сделать что-то вроде:
async-af
источник
Я бы использовал хорошо протестированные (миллионы загрузок в неделю) модули pify и async . Если вы не знакомы с модулем async, я настоятельно рекомендую вам ознакомиться с его документами . Я видел, как несколько разработчиков тратили время на воссоздание своих методов или, что еще хуже, создание сложного в обслуживании асинхронного кода, когда асинхронные методы более высокого порядка упростят код.
источник