Use async aguardar com Array.map

170

Dado o seguinte código:

var arr = [1,2,3,4,5];

var results: number[] = await arr.map(async (item): Promise<number> => {
        await callAsynchronousOperation(item);
        return item + 1;
    });

que produz o seguinte erro:

TS2322: O tipo 'Promessa <número> []' não pode ser atribuído ao tipo 'número []'. O tipo 'Promessa <número> não pode ser atribuído ao tipo' número '.

Como posso corrigir isso? Como posso fazer async awaite Array.maptrabalhar juntos?

Alon
fonte
6
Por que você está tentando transformar uma operação síncrona em uma operação assíncrona? arr.map()é síncrono e não retorna uma promessa.
precisa
2
Você não pode enviar uma operação assíncrona para uma função, como map, que espera uma síncrona, e espera que funcione.
Heretic Monkey
1
@ jfriend00 Tenho muitas declarações de espera na função interna. Na verdade, é uma função longa e eu apenas a simplifiquei para torná-la legível. Adicionei agora uma chamada em espera para esclarecer por que deveria ser assíncrona.
Alon
Você precisa aguardar algo que retorne uma promessa, não algo que retorne uma matriz.
jfriend00
2
Uma coisa útil a entender é que toda vez que você marca uma função como async, está fazendo com que essa função retorne uma promessa. Então, é claro, um mapa de assíncrono retorna uma matriz de promessas :)
Anthony Manning-Franklin

Respostas:

380

O problema aqui é que você está tentando awaituma série de promessas, e não uma promessa. Isso não faz o que você espera.

Quando o objeto passado awaitnão é uma promessa, awaitsimplesmente retorna o valor como está imediatamente, em vez de tentar resolvê-lo. Portanto, desde que você passou awaituma matriz (de objetos Promise) aqui em vez de uma Promessa, o valor retornado por wait é simplesmente essa matriz, que é do tipo Promise<number>[].

O que você precisa fazer aqui é chamar Promise.alla matriz retornada por mappara convertê-la em uma única promessa antes de awaitrecebê-la.

De acordo com os documentos MDN paraPromise.all :

O Promise.all(iterable)método retorna uma promessa que é resolvida quando todas as promessas do argumento iterável foram resolvidas ou rejeitadas com o motivo da primeira promessa aprovada que rejeita.

Então, no seu caso:

var arr = [1, 2, 3, 4, 5];

var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
}));

Isso resolverá o erro específico que você está encontrando aqui.

Ajedi32
fonte
1
O que :significam os dois pontos?
Daniel diz Reinstate Monica
12
@DanielPendergast É para anotações de tipo no TypeScript.
Ajedi32
Qual é a diferença entre chamar callAsynchronousOperation(item);com e sem awaitdentro da função de mapa assíncrono?
nerdizzle 27/03
@nerdizzle Isso soa como um bom candidato para outra pergunta. Basicamente, porém, awaita função aguardará a conclusão da operação assíncrona (ou falhará) antes de continuar; caso contrário, ela continuará imediatamente sem aguardar.
Ajedi32 27/03
@ Ajedi32 thx pela resposta. Mas sem o aguardar no mapa assíncrono, não é mais possível aguardar o resultado da função?
nerdizzle 28/03
16

Existe outra solução para isso, se você não estiver usando promessas nativas, mas o Bluebird.

Você também pode tentar usar Promise.map () , misturando array.map e Promise.all

No seu caso:

  var arr = [1,2,3,4,5];

  var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
  });
Gabriel Cheung
fonte
2
É diferente - não executa todas as operações em paralelo, mas as executa em sequência.
Andrey Tserkus
5
@AndreyTserkus Promise.mapSeriesou Promise.eachsão sequenciais, Promise.mapinicia todos de uma vez.
Kiechlus
1
@AndreyTserkus, você pode executar todas ou algumas operações em paralelo, fornecendo a concurrencyopção
11
Vale ressaltar que não é um JS de baunilha.
22618 Michal
@Michal sim, é SyntaxError
CS QGB
6

Se você mapear para uma matriz de promessas, poderá resolvê-las todas para uma matriz de números. Consulte Promise.all .

Dan Beaulieu
fonte
2

Eu recomendo usar Promise.all, como mencionado acima, mas se você realmente quiser evitar essa abordagem, poderá fazer um loop for ou qualquer outro:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}
Beckster
fonte
6
O Promise.all será assíncrono para cada elemento da matriz. Isso será uma sincronização; é preciso esperar para concluir um elemento para iniciar o próximo.
Santiago Mendoza Ramírez
Para aqueles que tentam essa abordagem, observe que for..of é a maneira correta de iterar o conteúdo de uma matriz, enquanto for..in itera sobre os índices.
ralfoide 14/02
2

Solução abaixo para processar todos os elementos de uma matriz de forma assíncrona E preservar a ordem:

const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));

const calc = async n => {
  await randomDelay();
  return n * 2;
};

const asyncFunc = async () => {
  const unresolvedPromises = arr.map(n => calc(n));
  const results = await Promise.all(unresolvedPromises);
};

asyncFunc();

Também codepen .

Observe que apenas "aguardamos" o Promise.all. Chamamos calc sem "aguardar" várias vezes e coletamos uma série de promessas não resolvidas imediatamente. Então Promise.all aguarda a resolução de todos eles e retorna uma matriz com os valores resolvidos em ordem.

Miki
fonte