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

Ответы (29)

Конечно, код работает, но я почти уверен, что он не делает то, что вы ожидаете. Он просто запускает несколько асинхронных вызовов, но функция printFiles сразу же возвращается после этого.

Последовательное чтение

Если вы хотите читать файлы последовательно, вы действительно не можете использовать для каждого. Просто используйте вместо этого современный цикл for… of, в котором await будет работать должным образом:

async function printFiles () {
  const files = await getFilePaths();

  for (const file of files) {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }
}

Параллельное чтение

Если вы хотите читать файлы параллельно, вы действительно не можете использовать forEach. Каждый из вызовов функции обратного вызова async действительно возвращает обещание, но вы их отбрасываете, а не ждете. Просто используйте вместо него map, и вы можете дождаться массива обещаний, который вы получите с Promise.all:

async function printFiles () {
  const files = await getFilePaths();

  await Promise.all(files.map(async (file) => {
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  }));
}

Решение Берги прекрасно работает, когда fs основано на обещаниях. Для этого вы можете использовать bluebird, fs-extra или fs-prom.

Однако решение для библиотеки ноды fs выглядит следующим образом:

const result = await Promise.all(filePaths
    .map( async filePath => {
      const fileContents = await getAssetFromCache(filePath, async function() {

        // 1. Wrap with Promise    
        // 2. Return the result of the Promise
        return await new Promise((res, rej) => {
          fs.readFile(filePath, 'utf8', function(err, data) {
            if (data) {
              res(data);
            }
          });
        });
      });

      return fileContents;
    }));

Примечание: require ('fs') обязательно принимает функцию как 3-й аргумент, иначе выдает ошибку:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

Вот отличный пример использования async в цикле forEach.

Напишите свой собственный asyncForEach

асинхронная функция asyncForEach (массив, обратный вызов) {
    for (let index = 0; index 

Можно использовать вот так

await asyncForEach(array, async function(item,index,array){
     //await here
   }
)

Аналогично Антонио Валу p-итерация, альтернативный модуль npm async-af:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

function printFiles() {
  // since AsyncAF accepts promises or non-promises, there's no need to await here
  const files = getFilePaths();

  AsyncAF(files).forEach(async file => {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  });
}

printFiles();

В качестве альтернативы async-af имеет статический метод (log / logAF), который регистрирует результаты обещаний:

const AsyncAF = require('async-af');
const fs = require('fs-promise');

function printFiles() {
  const files = getFilePaths();

  AsyncAF(files).forEach(file => {
    AsyncAF.log(fs.readFile(file, 'utf8'));
  });
}

printFiles();

Однако главным преимуществом библиотеки является то, что вы можете связать асинхронные методы так, чтобы они выполняли что-то вроде:

const aaf = require('async-af');
const fs = require('fs-promise');

const printFiles = () => aaf(getFilePaths())
  .map(file => fs.readFile(file, 'utf8'))
  .forEach(file => aaf.log(file));

printFiles();

async-af

Я бы использовал проверенные (миллионы загрузок в неделю) модули pify и async. Если вы не знакомы с модулем async, я настоятельно рекомендую вам проверить его документы. Я видел, как несколько разработчиков тратят время на воссоздание своих методов или, что еще хуже, на создание сложного в обслуживании асинхронного кода, когда асинхронные методы более высокого порядка упрощают код.

const async = require ('async')
const fs = require ('fs-обещание')
const pify = require ('pify')

асинхронная функция getFilePaths () {
    return Promise.resolve ([
        './package.json',
        './package-lock.json',
    ]);
}

асинхронная функция printFiles () {
  const files = ждать getFilePaths ()

  await pify (async.eachSeries) (files, async (file) => {// <- запускать последовательно
  // ждем pify (async.each) (files, async (file) => {// <- запускаем параллельно
    const contents = ожидание fs.readFile (файл, 'utf8')
    console.log (содержимое)
  })
  console.log ('ГАМБОН')
}

printFiles (). then (() => {
    console.log ('ГАМБАННИ')
})
// ПОРЯДОК ОТЧЕТОВ:
// содержимое package.json
// содержимое package-lock.json
// ГАМБОН
// ГАМБАННИ
``

Сегодня наткнулся на несколько решений для этого. Запуск функций async await в цикле forEach Loop. Построив оболочку вокруг себя, мы можем добиться этого.

Более подробное объяснение того, как он работает внутри, для собственного forEach и почему он не может выполнять вызов асинхронной функции, а также другие подробности о различных методах представлены по ссылке здесь

Несколько способов, которыми это можно сделать, и они следующие:

Способ 1. Использование оболочки.

await (()=>{
     return new Promise((resolve,reject)=>{
       items.forEach(async (item,index)=>{
           try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
           count++;
           if(index === items.length-1){
             resolve('Done')
           }
         });
     });
    })();

Метод 2: Использование того же, что и универсальная функция Array.prototype

Array.prototype.forEachAsync.js

if(!Array.prototype.forEachAsync) {
    Array.prototype.forEachAsync = function (fn){
      return new Promise((resolve,reject)=>{
        this.forEach(async(item,index,array)=>{
            await fn(item,index,array);
            if(index === array.length-1){
                resolve('done');
            }
        })
      });
    };
  }

Использование:

require('./Array.prototype.forEachAsync');

let count = 0;

let hello = async (items) => {

// Method 1 - Using the Array.prototype.forEach 

    await items.forEachAsync(async () => {
         try{
               await someAPICall();
           } catch(e) {
              console.log(e)
           }
        count++;
    });

    console.log("count = " + count);
}

someAPICall = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("done") // or reject('error')
        }, 100);
    })
}

hello(['', '', '', '']); // hello([]) empty array is also be handled by default

Метод 3:

Использование Promise.all

  await Promise.all(items.map(async (item) => {
        await someAPICall();
        count++;
    }));

    console.log("count = " + count);

Метод 4: Традиционный цикл for или современный цикл for

// Method 4 - using for loop directly

// 1. Using the modern for(.. in..) loop
   for(item in items){

        await someAPICall();
        count++;
    }

//2. Using the traditional for loop 

    for(let i=0;i

Одно важное предупреждение : await + for .. метода и forEach + async на самом деле имеют разный эффект.

Наличие await внутри реального цикла for гарантирует, что все асинхронные вызовы выполняются один за другим. И способ forEach + async будет запускать все обещания одновременно, что быстрее, но иногда перегружено (, если вы выполняете какой-либо запрос к БД или посещаете некоторые веб-службы с ограничениями по объему и не хочу запускать 100000 звонков за раз).

Вы также можете использовать reduce + обещание(менее элегантно), если вы не используете async / await и хотите, чтобы файлы читались один за другим.

files.reduce((lastPromise, file) => 
 lastPromise.then(() => 
   fs.readFile(file, 'utf8')
 ), Promise.resolve()
)

Или вы можете создать forEachAsync для помощи, но в основном использовать то же самое для цикла for.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}

@ Берги уже дал ответ, как правильно вести себя в этом конкретном случае. Я не буду дублировать здесь.

Я хотел бы устранить разницу между использованием forEach и для цикла, когда дело доходит до async и await

как для каждого работает

Давайте посмотрим, как работает forEach. Согласно спецификации ECMAScript, MDN предоставляет реализацию, которая может использоваться как полифилл. Копирую и вставляю сюда с удалением комментариев.

Array.prototype.forEach = function (callback, thisArg) {
  if (this == null) {throw new TypeError ('Array.prototype.forEach вызывается при нулевом или неопределенном значении'); }
  var T, k;
  var O = объект (это);
  var len = O.length >>> 0;
  if (typeof callback! == "функция") {выбросить новую TypeError (callback + 'не является функцией'); }
  если (arguments.length> 1) {T = thisArg; }
  k = 0;
  в то время как (k 

Вернемся к вашему коду, давайте извлечем обратный вызов как функцию.

обратный вызов асинхронной функции (файл) {
  const contents = ожидание fs.readFile (файл, 'utf8')
  console.log (содержимое)
}

Итак, в основном callback возвращает обещание, поскольку оно объявлено с помощью async. Внутри forEach, обратный вызов просто вызывается обычным способом, если сам обратный вызов возвращает обещание, механизм javascript не будет ждать его разрешения или отклонения. Вместо этого он помещает обещание в очередь заданий и продолжает выполнение цикла.

Как насчет await fs.readFile (file, 'utf8') внутри обратного вызова?

В основном, когда ваш async callback получает шанс на выполнение, js-движок приостанавливает работу до тех пор, пока fs.readFile (file, 'utf8') не будет разрешен или отклонен, и возобновить выполнение асинхронной функции после выполнения. Таким образом, переменная contents хранит фактический результат из fs.readFile, а не обещания. Итак, console.log (contents) выводит содержимое файла, а не Promise

Почему for ... of работает?

, когда мы пишем общий для цикла, мы получаем больше контроля, чем forEach. Давайте проведем рефакторинг printFiles.

асинхронная функция printFiles () {
  const files = await getFilePaths () // Предположим, это работает нормально

  for (файл const файлов) {
    const contents = ожидание fs.readFile (файл, 'utf8')
    console.log (содержимое)
    // или ждем обратного вызова (файл)
  }
}

При оценке для цикла у нас есть обещание await внутри функции async, выполнение будет приостановлено до тех пор, пока обещание await не будет поселился. Итак, вы можете представить себе, что файлы читаются один за другим в определенном порядке.

Выполнять последовательно

Иногда нам действительно нужно, чтобы асинхронные функции выполнялись в последовательном порядке. Например, у меня есть несколько новых записей, хранящихся в массиве для сохранения в базе данных, и я хочу, чтобы они сохранялись в последовательном порядке, что означает, что сначала должна быть сохранена первая запись в массиве, а затем вторая, пока не будет сохранена последняя.

Вот пример:

const records = [1, 2, 3, 4];

асинхронная функция saveRecord (запись) {
  вернуть новое обещание ((решено, отклонено) => {
    setTimeout (() => {
      решено (`запись $ {запись} сохранена`)
    }, Math.random () * 500)
  });
}

асинхронная функция forEachSaveRecords (записи) {
  records.forEach (async (запись) => {
    const res = ждать saveRecord (запись);
    console.log (res);
  })
}

асинхронная функция дляSaveRecords (записи) {
  for (const запись записей) {
    const res = ждать saveRecord (запись);
    console.log (res);
  }
}
(async () => {
  console.log ("=== для сохраненных записей ===")
  ждать forofSaveRecords (записи)
  
  console.log ("=== для каждой записи сохранения ===")
  await forEachSaveRecords (записи)
}) ()

Я использую setTimeout для имитации процесса сохранения записи в базу данных - это асинхронно и стоит случайное время. При использовании для каждогозаписи сохраняются в неопределенном порядке, но при использовании для .. изони сохраняются последовательно.

довольно безболезненно добавить в файл пару методов, которые будут обрабатывать асинхронные данные в сериализованном порядке и придадут более традиционный вид вашему коду. Например:

module.exports = function () {
  var self = this;

  this.each = async (items, fn) => {
    if (items && items.length) {
      await Promise.all(
        items.map(async (item) => {
          await fn(item);
        }));
    }
  };

  this.reduce = async (items, fn, initialValue) => {
    await self.each(
      items, async (item) => {
        initialValue = await fn(initialValue, item);
      });
    return initialValue;
  };
};

теперь, предполагая, что он сохранен в './myAsync.js', вы можете сделать что-то подобное приведенному ниже в соседнем файле:

...
/* your server setup here */
...
var MyAsync = require('./myAsync');
var Cat = require('./models/Cat');
var Doje = require('./models/Doje');
var example = async () => {
  var myAsync = new MyAsync();
  var doje = await Doje.findOne({ name: 'Doje', noises: [] }).save();
  var cleanParams = [];

  // FOR EACH EXAMPLE
  await myAsync.each(['bork', 'concern', 'heck'], 
    async (elem) => {
      if (elem !== 'heck') {
        await doje.update({ $push: { 'noises': elem }});
      }
    });

  var cat = await Cat.findOne({ name: 'Nyan' });

  // REDUCE EXAMPLE
  var friendsOfNyanCat = await myAsync.reduce(cat.friends,
    async (catArray, friendId) => {
      var friend = await Friend.findById(friendId);
      if (friend.name !== 'Long cat') {
        catArray.push(friend.name);
      }
    }, []);
  // Assuming Long Cat was a friend of Nyan Cat...
  assert(friendsOfNyanCat.length === (cat.friends.length - 1));
}

В дополнение к @ ответ Берги, я хотел бы предложить третью альтернативу. Он очень похож на 2-й пример @ Bergi, но вместо ожидания каждого readFile по отдельности вы создаете массив обещаний, каждое из которых ожидаете в конце.

import fs from 'fs-promise';
async function printFiles () {
  const files = await getFilePaths();

  const promises = files.map((file) => fs.readFile(file, 'utf8'))

  const contents = await Promise.all(promises)

  contents.forEach(console.log);
}

Обратите внимание, что функция, переданная в .map (), не обязательно должна быть async, поскольку fs.readFile в любом случае возвращает объект Promise. Следовательно, promises - это массив объектов Promise, который можно отправить в Promise.all ().

В ответе @ Bergi консоль может регистрировать содержимое файлов в том порядке, в котором они были прочитаны. Например, если действительно маленький файл заканчивает чтение до действительно большого файла, он будет зарегистрирован первым, даже если маленький файл появится после большого файла в массиве files. Однако в моем методе выше вам гарантировано, что консоль будет регистрировать файлы в том же порядке, что и предоставленный массив.

С ES2018 вы можете значительно упростить все вышеперечисленные ответы на:

async function printFiles () {
  const files = await getFilePaths()

  for await (const contents of files.map(file => fs.readFile(file, 'utf8'))) {
    console.log(contents)
  }
}

См. Спецификацию: async-итерация предложения


10.09.2018: Этот ответ в последнее время привлекает много внимания, см. сообщение в блоге Акселя Раушмайера для получения дополнительной информации об асинхронной итерации.

Используя Task, Futurize и Traversable List, вы можете просто выполнить

async function printFiles() {
  const files = await getFiles();

  List(files).traverse( Task.of, f => readFile( f, 'utf-8'))
    .fork( console.error, console.log)
}

Вот как это настроить

import fs from 'fs';
import { futurize } from 'futurize';
import Task from 'data.task';
import { List } from 'immutable-ext';

const future = futurizeP(Task)
const readFile = future(fs.readFile)

Другой способ структурировать желаемый код -

const printFiles = files => 
  List(files).traverse( Task.of, fn => readFile( fn, 'utf-8'))
    .fork( console.error, console.log)

Или, возможно, даже более функционально ориентированный

// 90% of encodings are utf-8, making that use case super easy is prudent

// handy-library.js
export const readFile = f =>
  future(fs.readFile)( f, 'utf-8' )

export const arrayToTaskList = list => taskFn => 
  List(files).traverse( Task.of, taskFn ) 

export const readFiles = files =>
  arrayToTaskList( files, readFile )

export const printFiles = files => 
  readFiles(files).fork( console.error, console.log)

Затем из родительской функции

async function main() {
  /* awesome code with side-effects before */
  printFiles( await getFiles() );
  /* awesome code with side-effects after */
}

Если вам действительно нужна большая гибкость в кодировании, вы можете просто сделать это (для удовольствия, я использую предложенный оператор Pipe Forward)

import { curry, flip } from 'ramda'

export const readFile = fs.readFile 
  |> future,
  |> curry,
  |> flip

export const readFileUtf8 = readFile('utf-8')

PS - Я не пробовал этот код на консоли, возможно, есть опечатки ... "прямой фристайл, с вершины купола!" как сказали бы дети 90-х. : -p

Currently the Array.forEach prototype property doesn't support async operations, but we can create our own poly-fill to meet our needs.

// Example of asyncForEach Array poly-fill for NodeJs
// file: asyncForEach.js
// Define asynForEach function 
async function asyncForEach(iteratorFunction){
  let indexer = 0
  for(let data of this){
    await iteratorFunction(data, indexer)
    indexer++
  }
}
// Append it as an Array prototype property
Array.prototype.asyncForEach = asyncForEach
module.exports = {Array}

And that's it! You now have an async forEach method available on any arrays that are defined after these to operations.

Let's test it...

// Nodejs style
// file: someOtherFile.js

const readline = require('readline')
Array = require('./asyncForEach').Array
const log = console.log

// Create a stream interface
function createReader(options={prompt: '>'}){
  return readline.createInterface({
    input: process.stdin
    ,output: process.stdout
    ,prompt: options.prompt !== undefined ? options.prompt : '>'
  })
}
// Create a cli stream reader
async function getUserIn(question, options={prompt:'>'}){
  log(question)
  let reader = createReader(options)
  return new Promise((res)=>{
    reader.on('line', (answer)=>{
      process.stdout.cursorTo(0, 0)
      process.stdout.clearScreenDown()
      reader.close()
      res(answer)
    })
  })
}

let questions = [
  `What's your name`
  ,`What's your favorite programming language`
  ,`What's your favorite async function`
]
let responses = {}

async function getResponses(){
// Notice we have to prepend await before calling the async Array function
// in order for it to function as expected
  await questions.asyncForEach(async function(question, index){
    let answer = await getUserIn(question)
    responses[question] = answer
  })
}

async function main(){
  await getResponses()
  log(responses)
}
main()
// Should prompt user for an answer to each question and then 
// log each question and answer as an object to the terminal

We could do the same for some of the other array functions like map...

async function asyncMap(iteratorFunction){
  let newMap = []
  let indexer = 0
  for(let data of this){
    newMap[indexer] = await iteratorFunction(data, indexer, this)
    indexer++
  }
  return newMap
}

Array.prototype.asyncMap = asyncMap

... and so on :)

Some things to note:

  • Your iteratorFunction must be an async function or promise
  • Any arrays created before Array.prototype. = will not have this feature available

Просто добавляю к исходному ответу

  • Синтаксис параллельного чтения в исходном ответе иногда сбивает с толку и труден для чтения, возможно, мы можем написать его другим подходом
async function printFiles() {
  const files = await getFilePaths();
  const fileReadPromises = [];

  const readAndLogFile = async filePath => {
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
    return contents;
  };

  files.forEach(file => {
    fileReadPromises.push(readAndLogFile(file));
  });

  await Promise.all(fileReadPromises);
}

  • Для последовательной работы, а не только for ... of, нормальный цикл for также будет работать
async function printFiles() {
  const files = await getFilePaths();

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const contents = await fs.readFile(file, "utf8");
    console.log(contents);
  }
}

Чтобы увидеть, как это может пойти не так, напечатайте console.log в конце метода.

Что в целом может пойти не так:

  • Произвольный заказ.
  • printFiles может завершиться перед печатью файлов.
  • Низкая производительность.

Это не всегда неверно, но часто встречается в стандартных случаях использования.

Обычно использование forEach приводит ко всем, кроме последнего. Он будет вызывать каждую функцию, не дожидаясь ее появления, что означает, что он сообщает всем функциям о запуске, а затем завершает работу, не дожидаясь завершения функций.

импортировать fs из 'fs-prom'

асинхронная функция printFiles () {
  const files = (ждать getFilePaths ()). map (файл => fs.readFile (файл, 'utf8'))

  for (файл const файлов)
    console.log (файл ожидания)
}

printFiles ()

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

Это будет:

  • Инициировать параллельное чтение всех файлов.
  • Сохраните порядок, используя map для сопоставления имен файлов с обещаниями, которые нужно ждать.
  • Дождитесь каждого обещания в порядке, определенном массивом.

В этом решении первый файл будет показан, как только он станет доступным, без необходимости ждать, пока другие станут доступны в первую очередь.

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

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

В версиях, которые читают файл за раз, затем останавливается в случае сбоя, не тратя время на попытки прочитать другие файлы. Даже с тщательно продуманной системой отмены может быть трудно избежать сбоя в первом файле, но уже при чтении большинства других файлов.

Производительность не всегда предсказуема. Хотя многие системы будут работать быстрее с параллельным чтением файлов, некоторые предпочтут последовательное. Некоторые из них являются динамическими и могут смещаться под нагрузкой. Оптимизация, предполагающая задержку, не всегда обеспечивает хорошую пропускную способность в условиях сильной конкуренции.

В этом примере также нет обработки ошибок. Если что-то требует, чтобы они либо все были успешно показаны, либо не показывались вообще, этого не произойдет.

Рекомендуется поэкспериментировать с console.log на каждом этапе и решениями для чтения поддельных файлов (вместо этого - случайная задержка). Хотя многие решения, кажется, делают то же самое в простых случаях, все они имеют тонкие различия, которые требуют некоторой дополнительной проверки, чтобы выявить их.

Используйте этот макет, чтобы определить разницу между решениями:

(async () => {
  const start = + новая дата ();
  const mock = () => {
    возвращение {
      fs: {readFile: file => new Promise ((разрешить, отклонить) => {
        // Вместо этого просто создайте три файла и попробуйте каждое временное расположение.
        // IE, все одинаково, [100, 200, 300], [300, 200, 100], [100, 300, 200] и т. Д.
        const time = Math.round (100 + Math.random () * 4900);
        console.log (`Чтение $ {file} началось с $ {new Date () - start} и займет $ {time} мс. ')
        setTimeout (() => {
          // Бонусный материал здесь, если вместо этого будет случайный отказ.
          console.log (`Чтение $ {file} завершено, обещание выполняется в $ {new Date () - start}.`);
          решить (файл);
        }, время);
      })},
      console: {log: file => console.log (`Журнал консоли $ {file} завершен в $ {new Date () - start} .`)},
      getFilePaths: () => ['A', 'B', 'C', 'D', 'E']
    };
  };

  const printFiles = (({fs, console, getFilePaths}) => {
    return async function () {
      const files = (ждать getFilePaths ()). map (file => fs.readFile (file, 'utf8'));

      for (файл const файлов)
        console.log (файл ожидания);
    };
  })(насмехаться());

  console.log (`Выполняется в $ {new Date () - start}`);
  ждать printFiles ();
  console.log (`Завершено в $ {new Date () - start}`);
}) ();

Like @Bergi's response, but with one difference.

Promise.all rejects all promises if one gets rejected.

So, use a recursion.

const readFilesQueue = async (files, index = 0) {
    const contents = await fs.readFile(files[index], 'utf8')
    console.log(contents)

    return files.length <= index
        ? readFilesQueue(files, ++index)
        : files

}

const printFiles async = () => {
    const files = await getFilePaths();
    const printContents = await readFilesQueue(files)

    return printContents
}

printFiles()

PS

readFilesQueue is outside of printFiles cause the side effect* introduced by console.log, it's better to mock, test, and or spy so, it's not cool to have a function that returns the content(sidenote).

Therefore, the code can simply be designed by that: three separated functions that are "pure"** and introduce no side effects, process the entire list and can easily be modified to handle failed cases.

const files = await getFilesPath()

const printFile = async (file) => {
    const content = await fs.readFile(file, 'utf8')
    console.log(content)
}

const readFiles = async = (files, index = 0) => {
    await printFile(files[index])

    return files.lengh <= index
        ? readFiles(files, ++index)
        : files
}

readFiles(files)

Future edit/current state

Node supports top-level await (this doesn't have a plugin yet, won't have and can be enabled via harmony flags), it's cool but doesn't solve one problem (strategically I work only on LTS versions). How to get the files?

Using composition. Given the code, causes to me a sensation that this is inside a module, so, should have a function to do it. If not, you should use an IIFE to wrap the role code into an async function creating simple module that's do all for you, or you can go with the right way, there is, composition.

// more complex version with IIFE to a single module
(async (files) => readFiles(await files())(getFilesPath)

Note that the name of variable changes due to semantics. You pass a functor (a function that can be invoked by another function) and recieves a pointer on memory that contains the initial block of logic of the application.

But, if's not a module and you need to export the logic?

Wrap the functions in a async function.

export const readFilesQueue = async () => {
    // ... to code goes here
}

Or change the names of variables, whatever...


* by side effect menans any colacteral effect of application that can change the statate/behaviour or introuce bugs in the application, like IO.

** by "pure", it's in apostrophe since the functions it's not pure and the code can be converged to a pure version, when there's no console output, only data manipulations.

Aside this, to be pure, you'll need to work with monads that handles the side effect, that are error prone, and treats that error separately of the application.

You can use Array.prototype.forEach, but async/await is not so compatible. This is because the promise returned from an async callback expects to be resolved, but Array.prototype.forEach does not resolve any promises from the execution of its callback. So then, you can use forEach, but you'll have to handle the promise resolution yourself.

Here is a way to read and print each file in series using Array.prototype.forEach

async function printFilesInSeries () {
  const files = await getFilePaths()

  let promiseChain = Promise.resolve()
  files.forEach((file) => {
    promiseChain = promiseChain.then(() => {
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    })
  })
  await promiseChain
}

Here is a way (still using Array.prototype.forEach) to print the contents of files in parallel

async function printFilesInParallel () {
  const files = await getFilePaths()

  const promises = []
  files.forEach((file) => {
    promises.push(
      fs.readFile(file, 'utf8').then((contents) => {
        console.log(contents)
      })
    )
  })
  await Promise.all(promises)
}

If you'd like to iterate over all elements concurrently:

async function asyncForEach(arr, fn) {
  await Promise.all(arr.map(fn));
}

If you'd like to iterate over all elements non-concurrently (e.g. when your mapping function has side effects or running mapper over all array elements at once would be too resource costly):

Option A: Promises

function asyncForEachStrict(arr, fn) {
  return new Promise((resolve) => {
    arr.reduce(
      (promise, cur, idx) => promise
        .then(() => fn(cur, idx, arr)),
      Promise.resolve(),
    ).then(() => resolve());
  });
}

Option B: async/await

async function asyncForEachStrict(arr, fn) {
  for (let idx = 0; idx < arr.length; idx += 1) {
    const cur = arr[idx];

    await fn(cur, idx, arr);
  }
}

As other answers have mentioned, you're probably wanting it to be executed in sequence rather in parallel. Ie. run for first file, wait until it's done, then once it's done run for second file. That's not what will happen.

I think it's important to address why this doesn't happen.

Think about how forEach works. I can't find the source, but I presume it works something like this:

const forEach = (arr, cb) => {
  for (let i = 0; i < arr.length; i++) {
    cb(arr[i]);
  }
};

Now think about what happens when you do something like this:

forEach(files, async logFile(file) {
  const contents = await fs.readFile(file, 'utf8');
  console.log(contents);
});

Inside forEach's for loop we're calling cb(arr[i]), which ends up being logFile(file). The logFile function has an await inside it, so maybe the for loop will wait for this await before proceeding to i++?

No, it won't. Confusingly, that's not how await works. From the docs:

An await splits execution flow, allowing the caller of the async function to resume execution. After the await defers the continuation of the async function, execution of subsequent statements ensues. If this await is the last expression executed by its function execution continues by returning to the function's caller a pending Promise for completion of the await's function and resuming execution of that caller.

So if you have the following, the numbers won't be logged before "b":

const delay = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

const logNumbers = async () => {
  console.log(1);
  await delay(2000);
  console.log(2);
  await delay(2000);
  console.log(3);
};

const main = () => {
  console.log("a");
  logNumbers();
  console.log("b");
};

main();

Circling back to forEach, forEach is like main and logFile is like logNumbers. main won't stop just because logNumbers does some awaiting, and forEach won't stop just because logFile does some awaiting.

Нецелесообразно вызывать асинхронный метод из цикла. Это связано с тем, что каждая итерация цикла будет откладываться до завершения всей асинхронной операции. Это не очень эффективно. Это также предотвращает преимущества преимуществ распараллеливания async/await.

Лучшим решением было бы создать все обещания сразу, а затем получить доступ к результатам с помощью Promise.all (). В противном случае каждая последующая операция не начнется, пока не будет завершена предыдущая.

Следовательно, код может быть реорганизован следующим образом:

const printFiles = async () => {
  const files = ждать getFilePaths ();
  const results = [];
  files.forEach ((файл) => {
    results.push (fs.readFile (файл, 'utf8'));
  });
  const content = await Promise.all (результаты);
  console.log (содержимое);
}

Простое решение для замены forEach () цикл ожидания, который не работает, заменяет forEach на map и добавляет Promise .all ( до начала.

Например:

ждать y.forEach (async (x) => {

От

до

await Promise.all (y.map (async (x) => {

В конце нужен дополнительный ).

Картинка объемом 1000 слов - только для последовательного подхода


Фон: Прошлой ночью я был в похожей ситуации. Я использовал асинхронную функцию в качестве аргумента foreach. Результат был непредсказуемым. Когда я 3 раза тестировал свой код, он 2 раза работал без проблем и 1 раз не удался. (что-то странное)

Наконец я собрался с мыслями и провел несколько тестов с блокнотом.

Сценарий 1 - Насколько непоследовательным может быть асинхронность в foreach

enter image description here

const getPromise = (время) => {
  вернуть новое обещание ((разрешить, отклонить) => {
    setTimeout (() => {
      решить (`Обещание выполнено за $ {time} s`)
    }, время)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise (1000), getPromise (500), getPromise (3000)]
  console.log («Перед каждым циклом»)

  myPromiseArray.forEach (async (element, index) => {
    let result = await element;
    console.log (результат);
  })

  console.log ('После для каждого цикла')
}

главный();

Сценарий 2 - Использование for - of, как указано выше в @Bergi

enter image description here

const getPromise = (время) => {
  вернуть новое обещание ((разрешить, отклонить) => {
    setTimeout (() => {
      решить (`Обещание выполнено за $ {time} s`)
    }, время)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise (1000), getPromise (500), getPromise (3000)]
  console.log («Перед каждым циклом»)

  // ИЗБЕГАЙТЕ ЭТОГО ИСПОЛЬЗОВАНИЯ
  // myPromiseArray.forEach (async (element, index) => {
  // let result = await element;
  // console.log (результат);
  //})

  // Это хорошо работает
  for (элемент константы myPromiseArray) {
    let result = await element;
    console.log (результат)
  }

  console.log ('После для каждого цикла')
}

главный();

Если вы такой же олдскульный, как я, вы можете просто использовать классический цикл for, который тоже работает:)

const getPromise = (время) => {
  вернуть новое обещание ((разрешить, отклонить) => {
    setTimeout (() => {
      решить (`Обещание выполнено за $ {time} s`)
    }, время)
  })
}

const main = async () => {
  const myPromiseArray = [getPromise (1000), getPromise (500), getPromise (3000)]
  console.log («Перед каждым циклом»)

  // ИЗБЕГАЙТЕ ЭТОГО ИСПОЛЬЗОВАНИЯ
  // myPromiseArray.forEach (async (element, index) => {
  // let result = await element;
  // console.log (результат);
  //})

  // Это тоже хорошо работает - классический цикл for :)
  for (let i = 0; i 

Надеюсь, это кому-то поможет, добрый день, ура!

Вот несколько прототипов forEachAsync. Обратите внимание: вам нужно ждать их:

Array.prototype.forEachAsync = async function (fn) {
    for (let t of this) { await fn(t) }
}

Array.prototype.forEachAsyncParallel = async function (fn) {
    await Promise.all(this.map(fn));
}

Примечание, хотя вы можете включить это в свой собственный код, вы не должны включать это в библиотеки, которые вы распространяете другим (чтобы не загрязнять их глобальные файлы).

Вы можете использовать цикл async.forEach из пакета async:

async.forEach(dataToLoop(array), async(data, cb) => {
                variable = await MongoQuery;
            }, function(err) {
                console.log(err);  
              })
            })
            .catch((err)=>{
              console.log(err);
            })

Оба вышеперечисленных решения работают, однако Антонио выполняет свою работу с меньшим количеством кода, вот как это помогло мне разрешить данные из моей базы данных из нескольких разных дочерних ссылок, а затем поместить их все в массив и разрешить его в обещании после того, как все сделано:

Promise.all(PacksList.map((pack)=>{
    return fireBaseRef.child(pack.folderPath).once('value',(snap)=>{
        snap.forEach( childSnap => {
            const file = childSnap.val()
            file.id = childSnap.key;
            allItems.push( file )
        })
    })
})).then(()=>store.dispatch( actions.allMockupItems(allItems)))

Если вы не можете использовать async / await (IE11, старый упаковщик и т. Д.), Вы можете попробовать эту рекурсивную функцию. В качестве асинхронного вызова я использовал fetch, но вы можете использовать любую функцию, возвращающую обещание.

var urlsToGet = ['https://google.com', 'https://yahoo.com'];

fetchOneAtATime (urlsToGet);

function fetchOneAtATime (urls) {
    if (urls.length === 0) {
        возвращение;
    }
    fetch (urls [0]). finally (() => fetchOneAtATime (urls.slice (1)));
}

Модуль p-iteration в npm реализует методы итерации Array, поэтому их можно очень просто использовать с помощью async / await.

Пример с вашим случаем:

const { forEach } = require('p-iteration');
const fs = require('fs-promise');

(async function printFiles () {
  const files = await getFilePaths();

  await forEach(files, async (file) => {
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  });
})();

Это решение также оптимизировано для памяти, поэтому вы можете запускать его для 10 000 элементов данных и запросов. Некоторые другие решения приведут к сбою сервера при работе с большими наборами данных.

В TypeScript:

export async function asyncForEach(array: Array, callback: (item: T, index: number) => Promise) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index);
        }
    }

Как пользоваться?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})

Вместо Promise.all в сочетании с Array.prototype.map (что не гарантирует порядок, в котором разрешаются Promises), Я использую Array.prototype.reduce, начиная с разрешенного Promise:

асинхронная функция printFiles () {
  const files = ждать getFilePaths ();

  await files.reduce (async (обещание, файл) => {
    // Эта строка будет ждать завершения последней асинхронной функции.
    // Первая итерация использует уже решенное обещание
    // итак, он немедленно продолжится.
    ждать обещания;
    const content = await fs.readFile (файл, 'utf8');
    console.log (содержимое);
  }, Promise.resolve ());
}

2022 WebDevInsider