Usando async / waitit com um loop forEach

1132

Existem problemas com o uso de async/ awaitem um forEachloop? Estou tentando percorrer uma matriz de arquivos e awaito conteúdo de cada arquivo.

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()

Esse código funciona, mas poderia algo dar errado com isso? Alguém me disse que você não deveria usar async/ awaitem uma função de ordem superior como essa, então eu só queria perguntar se havia algum problema com isso.

saadq
fonte

Respostas:

2155

Claro que o código funciona, mas tenho certeza que ele não faz o que você espera que ele faça. Ele dispara várias chamadas assíncronas, mas a printFilesfunção retorna imediatamente depois disso.

Lendo em sequência

Se você quiser ler os arquivos em sequência, não poderá usá-loforEach . Basta usar um for … ofloop moderno , no qual awaitfuncionará conforme o esperado:

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

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

Lendo em paralelo

Se você quiser ler os arquivos em paralelo, não poderá usá-loforEach . Cada uma das asyncchamadas da função de retorno de chamada retorna uma promessa, mas você as joga fora em vez de aguardá-las. Basta usar em mapvez disso, e você pode aguardar a série de promessas que receberá 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)
  }));
}
Bergi
fonte
33
Poderia explicar por que for ... of ...funciona?
precisa saber é o seguinte
84
ok, eu sei por que ... Usar Babel transformará a função de gerador async/ awaite usar forEachsignifica que cada iteração tem uma função de gerador individual, que não tem nada a ver com as outras. para que sejam executados independentemente e não tenham contexto next()com os outros. Na verdade, um for()loop simples também funciona porque as iterações também estão em uma única função geradora.
precisa saber é o seguinte
21
@ Dembane: Em resumo, porque foi projetado para funcionar :-) awaitsuspende a avaliação da função atual , incluindo todas as estruturas de controle. Sim, é bastante semelhante aos geradores a esse respeito (e é por isso que eles são usados ​​para polyfill assíncrono / aguardar).
Bergi 15/08/16
3
@ arve0 Na verdade, uma asyncfunção é bem diferente de um Promiseretorno de chamada do executor, mas sim, o mapretorno de chamada retorna uma promessa nos dois casos.
Bergi 29/03
5
Quando você aprender sobre as promessas de JS, use meia hora traduzindo latim. Espero que você esteja orgulhoso @Bergi;)
Félix Gagnon-Grenier
190

Com o ES2018, você pode simplificar bastante todas as respostas acima para:

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

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

Consulte especificação: proposta-assíncrona-iteração


2018-09-10: Esta resposta tem recebido muita atenção recentemente, consulte a publicação no blog de Axel Rauschmayer para obter mais informações sobre iteração assíncrona: ES2018: iteração assíncrona

Francisco Mateo
fonte
4
Com o voto positivo, seria ótimo se você pudesse colocar um link para a especificação em sua resposta para quem quiser saber mais sobre a iteração assíncrona.
Saadq
8
Ele não deve ser conteúdo em vez de arquivo no iterador
FluffyBeing
10
Por que as pessoas estão votando esta resposta? Dê uma olhada na resposta, na pergunta e na proposta. Após o ofdeve ser a função assíncrona que retornará uma matriz. Não funciona e Francisco disse;
Yevhenii Herasymchuk 8/01/19
3
Concordo totalmente com @AntonioVal. Não é uma resposta.
Yevhenii Herasymchuk
2
Embora eu concorde que não é uma resposta, a votação de uma proposta é uma maneira de aumentar sua popularidade, potencialmente disponibilizando-a para uso posterior mais tarde.
Robert Molina
62

Em vez de Promise.allem conjunto com Array.prototype.map(que não garante a ordem na qual os Promises são resolvidos), eu uso Array.prototype.reduce, começando com um resolvido Promise:

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

  await files.reduce(async (promise, file) => {
    // This line will wait for the last async function to finish.
    // The first iteration uses an already resolved Promise
    // so, it will immediately continue.
    await promise;
    const contents = await fs.readFile(file, 'utf8');
    console.log(contents);
  }, Promise.resolve());
}
Timothy Zorn
fonte
1
Isso funciona perfeitamente, muito obrigado. Você poderia explicar o que está acontecendo aqui com Promise.resolve()e await promise;?
parrker9
1
Isso é bem legal. Estou certo ao pensar que os arquivos serão lidos em ordem e não todos de uma vez?
precisa saber é o seguinte
1
@ parrker9 Promise.resolve()retorna um Promiseobjeto já resolvido , de modo que ele reduceprecisa Promisecomeçar. await promise;aguardará o último Promiseda cadeia resolver. @GollyJer Os arquivos serão processados ​​seqüencialmente, um de cada vez.
Timothy Zorn
Uso muito legal de reduzir, obrigado pelo comentário! Apenas denotarei que, ao contrário de outros métodos mencionados nos comentários, este é síncrono, o que significa que os arquivos são lidos um após o outro e não em paralelo (uma vez que a próxima iteração da função de redução depende da anterior iteração, deve ser síncrona).
Shay Yzhakov
1
@ Shay, você quer dizer sequencial, não síncrono. Isso ainda é assíncrono - se outras coisas forem agendadas, elas serão executadas entre as iterações aqui.
Timothy Zorn
33

O módulo de iteração p no npm implementa os métodos de iteração Array, para que possam ser usados ​​de maneira muito direta com async / waitit.

Um exemplo com o seu caso:

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);
  });
})();
Antonio Val
fonte
1
Eu gosto disso, pois tem as mesmas funções / métodos que o próprio JS - no meu caso, eu precisava somemais do que forEach. Obrigado!
Mikemaccana
26

Aqui estão alguns forEachAsyncprotótipos. Observe que você precisará awaitdeles:

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

Observe que, embora você possa incluí-lo em seu próprio código, não deve incluí-lo nas bibliotecas que distribui para outras pessoas (para evitar poluir seus globais).

Matt
fonte
1
Embora eu hesite em adicionar coisas diretamente para o protótipo, esta é uma aplicação agradável assíncrona forEach
DaniOcean
2
Contanto que o nome seja único no futuro (como eu usaria _forEachAsync), isso é razoável. Eu também acho que é a melhor resposta, pois economiza muito código clichê.
Mikemaccana #
1
@estus Isso é para evitar poluir o código de outras pessoas. Se o código pertencer à nossa organização pessoal, e os globais estiverem em um arquivo bem identificado ( globals.jsseria bom), podemos adicionar globais como quisermos.
Mikemaccana 26/04/19
1
@mikemaccana Isso é para evitar más práticas geralmente aceitas. Isso é verdade, isso pode ser feito desde que você use apenas o código original, o que acontece raramente. O problema é que, quando você usa libs de terceiros, pode haver outro cara que se sente da mesma maneira e modifica os mesmos globais, apenas porque parecia uma boa idéia no momento em que uma lib foi escrita.
Estus Flask
1
@estus Claro. Adicionei um aviso à pergunta para salvar a discussão (não particularmente produtiva) aqui.
Mikemaccana 26/04/19
7

Além da resposta de @ Bergi , gostaria de oferecer uma terceira alternativa. É muito parecido com o segundo exemplo de @ Bergi, mas em vez de esperar cada um readFileindividualmente, você cria uma série de promessas, cada uma das quais espera no final.

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

Observe que a função passada para .map()não precisa ser async, pois fs.readFileretorna um objeto Promise de qualquer maneira. Portanto, promiseshá uma matriz de objetos Promise, que podem ser enviados para Promise.all().

Na resposta de @ Bergi, o console pode registrar o conteúdo do arquivo na ordem em que são lidos. Por exemplo, se um arquivo muito pequeno terminar a leitura antes de um arquivo muito grande, ele será registrado primeiro, mesmo se o arquivo pequeno vier após o arquivo grande na filesmatriz. No entanto, no meu método acima, você tem a garantia de que o console registrará os arquivos na mesma ordem que a matriz fornecida.

chharvey
fonte
1
Tenho certeza de que você está incorreto: tenho certeza de que seu método também pode ler os arquivos fora de ordem. Sim, ele registrará a saída na ordem correta (devido à await Promise.all), mas os arquivos podem ter sido lidos em uma ordem diferente, o que contradiz sua afirmação "você está garantido que o console registrará os arquivos na mesma ordem em que são ler".
Venryx 11/11/19
1
@ Venryx Você está certo, obrigado pela correção. Eu atualizei minha resposta.
chharvey
6

A solução da Bergi funciona bem quando fsé baseada em promessas. Você pode usar bluebird, fs-extraou fs-promisepara isso.

No entanto, a solução para a fsbiblioteca nativa do nó é a seguinte:

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

Nota: require('fs') compulsoriamente assume a função como terceiro argumento; caso contrário, gera erro:

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
myDoggyWritesCode
fonte
5

Ambas as soluções acima funcionam, no entanto, o Antonio's faz o trabalho com menos código; eis como me ajudou a resolver dados do meu banco de dados, de várias referências filho diferentes e, em seguida, empurrando todos eles em uma matriz e resolvendo-os em uma promessa, afinal é feito:

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)))
Hooman Askari
fonte
4

é muito simples exibir alguns métodos em um arquivo que manipularão dados assíncronos em uma ordem serializada e darão um sabor mais convencional ao seu código. Por exemplo:

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

agora, supondo que isso esteja salvo em './myAsync.js', você pode fazer algo semelhante ao abaixo em um arquivo adjacente:

...
/* 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));
}
Jay Edwards
fonte
2
Adendo menor, não se esqueça de embrulhar seus wait / assíncronos em blocos try / catch !!
Jay Edwards
4

Como a resposta de Bergi, mas com uma diferença.

Promise.all rejeita todas as promessas se alguém for rejeitado.

Então, use uma recursão.

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

readFilesQueueestá fora da printFilescausa, o efeito colateral * introduzido porconsole.log , é melhor zombar, testar e / ou espionar, para que não seja legal ter uma função que retorne o conteúdo (nota de rodapé).

Portanto, o código pode simplesmente ser projetado com isso: três funções separadas que são "puras" ** e não apresentam efeitos colaterais, processam a lista inteira e podem ser facilmente modificadas para lidar com casos com falha.

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)

Edição futura / estado atual

O nó suporta espera de nível superior (isso ainda não possui um plug-in, não terá e pode ser ativado por meio de sinalizadores de harmonia), é legal, mas não resolve um problema (estrategicamente eu trabalho apenas nas versões LTS). Como obter os arquivos?

Usando composição. Dado o código, me causa uma sensação de que isso está dentro de um módulo, portanto, deve ter uma função para fazê-lo. Caso contrário, você deve usar um IIFE para agrupar o código de função em uma função assíncrona, criando um módulo simples que faz tudo por você ou você pode seguir o caminho certo, existe a composição.

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

Observe que o nome da variável muda devido à semântica. Você passa um functor (uma função que pode ser chamada por outra função) e recebe um ponteiro na memória que contém o bloco inicial de lógica do aplicativo.

Mas, se não é um módulo e você precisa exportar a lógica?

Quebra as funções em uma função assíncrona.

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

Ou mude os nomes das variáveis, qualquer que seja ...


* o efeito colateral menans qualquer efeito colacteral da aplicação que possa alterar o estado / comportamento ou introduzir bugs na aplicação, como E / S.

** por "puro", está em apóstrofo, pois as funções não são puras e o código pode ser convertido para uma versão pura, quando não há saída do console, apenas manipulação de dados.

Além disso, para ser puro, você precisará trabalhar com mônadas que lidam com o efeito colateral, propensas a erros e tratam esse erro separadamente do aplicativo.

lukaswilkeer
fonte
3

Uma ressalva importante é: o await + for .. ofmétodo e o forEach + asynccaminho realmente têm efeitos diferentes.

Ter awaitum forloop real garantirá que todas as chamadas assíncronas sejam executadas uma a uma. E o forEach + asynccaminho disparará todas as promessas ao mesmo tempo, o que é mais rápido, mas às vezes sobrecarregado ( se você fizer alguma consulta ao banco de dados ou visitar alguns serviços da web com restrições de volume e não desejar acionar 100.000 chamadas por vez).

Você também pode usar reduce + promise(menos elegante) se não o usar async/awaite quiser garantir que os arquivos sejam lidos um após o outro .

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

Ou você pode criar um forEachAsync para ajudar, mas basicamente usa o mesmo para o loop subjacente.

Array.prototype.forEachAsync = async function(cb){
    for(let x of this){
        await cb(x);
    }
}
LeOn - Han Li
fonte
Dê uma olhada em Como definir o método em javascript em Array.prototype e Object.prototype para que ele não apareça no loop for . Além disso, você provavelmente deve usar a mesma iteração que a nativa forEach- acessando índices em vez de confiar na iterabilidade - e passar o índice para o retorno de chamada.
Bergi
Você pode usar Array.prototype.reducede uma maneira que use uma função assíncrona. Eu mostrei um exemplo na minha resposta: stackoverflow.com/a/49499491/2537258
Timothy Zorn
3

Utilizando Tarefa, futurizar e uma Lista atravessável, você pode simplesmente fazer

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

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

Aqui está como você configurou isso

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)

Outra maneira de estruturar o código desejado seria

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

Ou talvez ainda mais funcionalmente orientado

// 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)

Em seguida, a partir da função pai

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

Se você realmente quisesse mais flexibilidade na codificação, poderia fazer isso (por diversão, estou usando o operador Pipe Forward proposto )

import { curry, flip } from 'ramda'

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

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

PS - Eu não tentei esse código no console, pode ter alguns erros de digitação ... "estilo livre direto, fora do topo da cúpula!" como as crianças dos anos 90 diriam. :-p

Babakness
fonte
3

Atualmente, a propriedade do protótipo Array.forEach não suporta operações assíncronas, mas podemos criar nosso próprio preenchimento de poli para atender às nossas necessidades.

// 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}

E é isso! Agora você tem um método assíncrono forEach disponível em quaisquer matrizes definidas após essas operações.

Vamos testar ...

// 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

Poderíamos fazer o mesmo para algumas das outras funções do array, como 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

... e assim por diante :)

Algumas coisas a serem observadas:

  • Sua função iterator deve ser uma função ou promessa assíncrona
  • Todas as matrizes criadas antes Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>não terão esse recurso disponível
Beau
fonte
3

Apenas adicionando à resposta original

  • A sintaxe de leitura paralela na resposta original às vezes é confusa e difícil de ler, talvez possamos escrevê-la de uma maneira diferente
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);
}
  • Para operação seqüencial, não apenas para ... de , o loop normal também funcionará
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);
  }
}
gsaandy
fonte
2

Para ver como isso pode dar errado, imprima console.log no final do método.

Coisas que podem dar errado em geral:

  • Ordem arbitrária.
  • O printFiles pode terminar a execução antes da impressão dos arquivos.
  • Mau desempenho.

Eles nem sempre estão errados, mas frequentemente estão em casos de uso padrão.

Geralmente, o uso de forEach resultará em todos, exceto no último. Ele chamará cada função sem aguardar a função, o que significa que diz para todas as funções iniciarem e termina sem aguardar o término das funções.

import fs from 'fs-promise'

async function printFiles () {
  const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'))

  for(const file of files)
    console.log(await file)
}

printFiles()

Este é um exemplo em JS nativo que preservará a ordem, impedirá o retorno prematuro da função e, em teoria, manterá o desempenho ideal.

Isso vai:

  • Iniciar todas as leituras do arquivo para acontecer em paralelo.
  • Preserve a ordem usando o mapa para mapear nomes de arquivos com as promessas a aguardar.
  • Aguarde cada promessa na ordem definida pela matriz.

Com esta solução, o primeiro arquivo será mostrado assim que estiver disponível, sem precisar esperar que os outros estejam disponíveis primeiro.

Ele também carregará todos os arquivos ao mesmo tempo, em vez de esperar que o primeiro termine antes que a segunda leitura do arquivo possa ser iniciada.

O único inconveniente disso e da versão original é que, se várias leituras forem iniciadas ao mesmo tempo, é mais difícil lidar com erros, pois há mais erros que podem acontecer ao mesmo tempo.

Nas versões que lêem um arquivo de cada vez, ele pára em uma falha sem perder tempo tentando ler mais arquivos. Mesmo com um sistema de cancelamento elaborado, pode ser difícil evitar falhas no primeiro arquivo, mas a leitura da maioria dos outros arquivos também.

O desempenho nem sempre é previsível. Enquanto muitos sistemas serão mais rápidos com leituras paralelas de arquivos, alguns preferirão sequenciais. Alguns são dinâmicos e podem mudar sob carga, otimizações que oferecem latência nem sempre produzem um bom rendimento sob contenção pesada.

Também não há tratamento de erros nesse exemplo. Se algo exige que todos sejam mostrados com sucesso ou não, isso não será possível.

Recomenda-se a experimentação aprofundada com o console.log em cada estágio e soluções falsas de leitura de arquivos (em vez de atraso aleatório). Embora muitas soluções pareçam fazer o mesmo em casos simples, todas têm diferenças sutis que exigem um exame minucioso extra.

Use esse mock para ajudar a diferenciar as soluções:

(async () => {
  const start = +new Date();
  const mock = () => {
    return {
      fs: {readFile: file => new Promise((resolve, reject) => {
        // Instead of this just make three files and try each timing arrangement.
        // IE, all same, [100, 200, 300], [300, 200, 100], [100, 300, 200], etc.
        const time = Math.round(100 + Math.random() * 4900);
        console.log(`Read of ${file} started at ${new Date() - start} and will take ${time}ms.`)
        setTimeout(() => {
          // Bonus material here if random reject instead.
          console.log(`Read of ${file} finished, resolving promise at ${new Date() - start}.`);
          resolve(file);
        }, time);
      })},
      console: {log: file => console.log(`Console Log of ${file} finished at ${new Date() - start}.`)},
      getFilePaths: () => ['A', 'B', 'C', 'D', 'E']
    };
  };

  const printFiles = (({fs, console, getFilePaths}) => {
    return async function() {
      const files = (await getFilePaths()).map(file => fs.readFile(file, 'utf8'));

      for(const file of files)
        console.log(await file);
    };
  })(mock());

  console.log(`Running at ${new Date() - start}`);
  await printFiles();
  console.log(`Finished running at ${new Date() - start}`);
})();
jgmjgm
fonte
2

Hoje me deparei com várias soluções para isso. A execução do async aguarda funções no loop forEach. Ao criar o invólucro, podemos fazer isso acontecer.

Explicações mais detalhadas sobre como ele funciona internamente, para o nativo forEach e por que ele não é capaz de fazer uma chamada de função assíncrona e outros detalhes sobre os vários métodos são fornecidos no link aqui

As várias maneiras pelas quais isso pode ser feito e são as seguintes,

Método 1: usando o wrapper.

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

Método 2: usando o mesmo que uma função genérica de 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');
            }
        })
      });
    };
  }

Uso:

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

Método 3:

Usando Promise.all

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

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

Método 4: tradicional para loop ou moderno para loop

// 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<items.length;i++){

        await someAPICall();
        count++;
    }


    console.log("count = " + count);
PranavKAndro
fonte
Seus métodos 1 e 2 são simplesmente implementações incorretas onde Promise.alldeveriam ter sido usadas - elas não levam em consideração nenhum dos muitos casos extremos.
Bergi 24/11/19
@ Bergi: Obrigado pelos comentários válidos. Você poderia me explicar por que os métodos 1 e 2 estão incorretos. Também serve ao propósito. Isso funciona muito bem. Isso significa que todos esses métodos são possíveis, com base na situação em que se pode escolher um. Eu tenho o exemplo em execução para o mesmo.
PranavKAndro
Ele falha em matrizes vazias, não possui nenhum tratamento de erros e provavelmente mais problemas. Não reinvente a roda. Basta usar Promise.all.
Bergi 25/11/19
Em certas condições, onde não é possível, será útil. Além disso, o tratamento de erros é feito pela API forEach por padrão, sem problemas. É cuidado!
PranavKAndro 26/11/19
Não, não há condições onde Promise.allnão é possível, mas async/ awaité. E não, forEachabsolutamente não lida com nenhum erro de promessa.
#
2

Essa solução também é otimizada para memória, para que você possa executá-la em 10.000 itens de dados e solicitações. Algumas das outras soluções aqui travam o servidor em grandes conjuntos de dados.

No TypeScript:

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

Como usar?

await asyncForEach(receipts, async (eachItem) => {
    await ...
})
Oliver Dixon
fonte
2

Você pode usar Array.prototype.forEach, mas async / waitit não é tão compatível. Isso ocorre porque a promessa retornada de um retorno de chamada assíncrona espera ser resolvida, mas Array.prototype.forEachnão resolve nenhuma promessa da execução de seu retorno de chamada. Portanto, você pode usar o forEach, mas precisará lidar com a resolução da promessa.

Aqui está uma maneira de ler e imprimir cada arquivo em série usando 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
}

Aqui está uma maneira (ainda usando Array.prototype.forEach) de imprimir o conteúdo dos arquivos em paralelo

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)
}
richytong
fonte
O primeiro senario é ideal para loops que precisam ser executados em série e você não pode usar para
Mark Odey
0

Semelhante ao de Antonio Val p-iteration, um módulo npm alternativo é 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();

Como alternativa, async-afpossui um método estático (log / logAF) que registra os resultados das promessas:

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

No entanto, a principal vantagem da biblioteca é que você pode encadear métodos assíncronos para fazer algo como:

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

Scott Rudiger
fonte
-3

Eu usaria os módulos pify e async bem testados (milhões de downloads por semana) . Se você não estiver familiarizado com o módulo assíncrono, eu recomendo que você verifique os documentos . Eu vi vários desenvolvedores desperdiçarem tempo recriando seus métodos, ou pior, dificultando a manutenção de códigos assíncronos quando métodos assíncronos de ordem superior simplificariam o código.

const async = require('async')
const fs = require('fs-promise')
const pify = require('pify')

async function getFilePaths() {
    return Promise.resolve([
        './package.json',
        './package-lock.json',
    ]);
}

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

  await pify(async.eachSeries)(files, async (file) => {  // <-- run in series
  // await pify(async.each)(files, async (file) => {  // <-- run in parallel
    const contents = await fs.readFile(file, 'utf8')
    console.log(contents)
  })
  console.log('HAMBONE')
}

printFiles().then(() => {
    console.log('HAMBUNNY')
})
// ORDER OF LOGS:
// package.json contents
// package-lock.json contents
// HAMBONE
// HAMBUNNY
```

Zachary Ryan Smith
fonte
Este é um passo na direção errada. Aqui está um guia de mapeamento que criei para ajudar as pessoas a ficarem paralisadas na era JS moderna: github.com/jmjpro/async-package-to-async-await/blob/master/… .
jbustamovej
Como você pode ver aqui , estou interessado e aberto a usar async / waitit em vez da lib async. Agora, acho que cada um tem um tempo e um lugar. Não estou convencido de que a lib assíncrona == "inferno de retorno de chamada" e async / waitit == "a era moderna do JS". imo, quando assíncrono lib> assíncrono / espera: 1. fluxo complexo (por exemplo, fila, carga e até automático quando as coisas ficam complicadas) 2. simultaneidade 3. matrizes / objetos / iterables de suporte 4. tratamento de erros
Zachary Ryan Smith