Existem problemas com o uso de async
/ await
em um forEach
loop? Estou tentando percorrer uma matriz de arquivos e await
o 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
/ await
em uma função de ordem superior como essa, então eu só queria perguntar se havia algum problema com isso.
for ... of ...
funciona?async
/await
e usarforEach
significa 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 contextonext()
com os outros. Na verdade, umfor()
loop simples também funciona porque as iterações também estão em uma única função geradora.await
suspende 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).async
função é bem diferente de umPromise
retorno de chamada do executor, mas sim, omap
retorno de chamada retorna uma promessa nos dois casos.Com o ES2018, você pode simplificar bastante todas as respostas acima para:
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
fonte
of
deve ser a função assíncrona que retornará uma matriz. Não funciona e Francisco disse;Em vez de
Promise.all
em conjunto comArray.prototype.map
(que não garante a ordem na qual osPromise
s são resolvidos), eu usoArray.prototype.reduce
, começando com um resolvidoPromise
:fonte
Promise.resolve()
eawait promise;
?Promise.resolve()
retorna umPromise
objeto já resolvido , de modo que elereduce
precisaPromise
começar.await promise;
aguardará o últimoPromise
da cadeia resolver. @GollyJer Os arquivos serão processados seqüencialmente, um de cada vez.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:
fonte
some
mais do queforEach
. Obrigado!Aqui estão alguns
forEachAsync
protótipos. Observe que você precisaráawait
deles: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).
fonte
_forEachAsync
), isso é razoável. Eu também acho que é a melhor resposta, pois economiza muito código clichê.globals.js
seria bom), podemos adicionar globais como quisermos.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
readFile
individualmente, você cria uma série de promessas, cada uma das quais espera no final.Observe que a função passada para
.map()
não precisa serasync
, poisfs.readFile
retorna um objeto Promise de qualquer maneira. Portanto,promises
há uma matriz de objetos Promise, que podem ser enviados paraPromise.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
files
matriz. 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.fonte
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".A solução da Bergi funciona bem quando
fs
é baseada em promessas. Você pode usarbluebird
,fs-extra
oufs-promise
para isso.No entanto, a solução para a
fs
biblioteca nativa do nó é a seguinte:Nota:
require('fs')
compulsoriamente assume a função como terceiro argumento; caso contrário, gera erro:fonte
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:
fonte
é 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:
agora, supondo que isso esteja salvo em './myAsync.js', você pode fazer algo semelhante ao abaixo em um arquivo adjacente:
fonte
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.
PS
readFilesQueue
está fora daprintFiles
causa, 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.
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.
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.
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.
fonte
Uma ressalva importante é: o
await + for .. of
método e oforEach + async
caminho realmente têm efeitos diferentes.Ter
await
umfor
loop real garantirá que todas as chamadas assíncronas sejam executadas uma a uma. E oforEach + async
caminho 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 usarasync/await
e quiser garantir que os arquivos sejam lidos um após o outro .Ou você pode criar um forEachAsync para ajudar, mas basicamente usa o mesmo para o loop subjacente.
fonte
forEach
- acessando índices em vez de confiar na iterabilidade - e passar o índice para o retorno de chamada.Array.prototype.reduce
de uma maneira que use uma função assíncrona. Eu mostrei um exemplo na minha resposta: stackoverflow.com/a/49499491/2537258Utilizando Tarefa, futurizar e uma Lista atravessável, você pode simplesmente fazer
Aqui está como você configurou isso
Outra maneira de estruturar o código desejado seria
Ou talvez ainda mais funcionalmente orientado
Em seguida, a partir da função pai
Se você realmente quisesse mais flexibilidade na codificação, poderia fazer isso (por diversão, estou usando o operador Pipe Forward proposto )
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
fonte
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.
E é isso! Agora você tem um método assíncrono forEach disponível em quaisquer matrizes definidas após essas operações.
Vamos testar ...
Poderíamos fazer o mesmo para algumas das outras funções do array, como map ...
... e assim por diante :)
Algumas coisas a serem observadas:
Array.prototype.<yourAsyncFunc> = <yourAsyncFunc>
não terão esse recurso disponívelfonte
Apenas adicionando à resposta original
fonte
Para ver como isso pode dar errado, imprima console.log no final do método.
Coisas que podem dar errado em geral:
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.
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:
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:
fonte
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.
Método 2: usando o mesmo que uma função genérica de Array.prototype
Array.prototype.forEachAsync.js
Uso:
Método 3:
Usando Promise.all
Método 4: tradicional para loop ou moderno para loop
fonte
Promise.all
deveriam ter sido usadas - elas não levam em consideração nenhum dos muitos casos extremos.Promise.all
.Promise.all
não é possível, masasync
/await
é. E não,forEach
absolutamente não lida com nenhum erro de promessa.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:
Como usar?
fonte
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, masArray.prototype.forEach
nã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
Aqui está uma maneira (ainda usando
Array.prototype.forEach
) de imprimir o conteúdo dos arquivos em paralelofonte
Semelhante ao de Antonio Val
p-iteration
, um módulo npm alternativo éasync-af
:Como alternativa,
async-af
possui um método estático (log / logAF) que registra os resultados das promessas:No entanto, a principal vantagem da biblioteca é que você pode encadear métodos assíncronos para fazer algo como:
async-af
fonte
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.
fonte