Usa async await con Array.map


171

Dato il seguente codice:

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

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

che produce il seguente errore:

TS2322: Digitare 'Promessa <numero> []' non è assegnabile al tipo 'numero []'. Digitare "Promessa <numero> non è assegnabile al tipo" numero ".

Come posso ripararlo? Come posso creare async awaite Array.maplavorare insieme?


6
Perché stai tentando di eseguire un'operazione sincrona in un'operazione asincrona? arr.map()è sincrono e non restituisce una promessa.
jfriend00

2
Non è possibile inviare un'operazione asincrona a una funzione, ad esempio map, che prevede un'operazione sincrona e si prevede che funzioni.
Heretic Monkey,

1
@ jfriend00 Ho molte dichiarazioni attese nella funzione interiore. In realtà è una funzione lunga e l'ho semplicemente semplificata per renderla leggibile. Ho aggiunto ora una chiamata in attesa per chiarire perché dovrebbe essere asincrono.
Alon,

Devi aspettare qualcosa che ritorni una promessa, non qualcosa che ritorni un array.
jfriend00,

2
Una cosa utile da capire è che ogni volta che contrassegni una funzione come async, stai facendo in modo che la funzione restituisca una promessa. Quindi, ovviamente, una mappa di asincrono restituisce una serie di promesse :)
Anthony Manning-Franklin,

Risposte:


382

Il problema qui è che stai provando awaituna serie di promesse piuttosto che una promessa. Questo non fa quello che ti aspetti.

Quando l'oggetto passato a awaitnon è una Promessa, awaitrestituisce semplicemente il valore così com'è immediatamente anziché tentare di risolverlo. Quindi, poiché hai passato awaitun array (di oggetti Promise) qui invece di un Promise, il valore restituito da wait è semplicemente quell'array, che è di tipo Promise<number>[].

Quello che devi fare qui è chiamare Promise.alll'array restituito mapper convertirlo in una singola Promessa prima di awaitinviarlo.

Secondo i documenti MDN perPromise.all :

Il Promise.all(iterable)metodo restituisce una promessa che si risolve quando tutte le promesse nell'argomento iterabile sono state risolte o vengono rifiutate con il motivo della promessa passata per prima che viene rifiutata.

Quindi nel tuo 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;
}));

Questo risolverà l'errore specifico che stai riscontrando qui.


1
Cosa significano i :due punti?
Daniel dice di reintegrare Monica il

12
@DanielPendergast È per le annotazioni dei tipi in TypeScript.
Ajedi32

Qual è la differenza tra chiamare callAsynchronousOperation(item);con e senza awaitall'interno della funzione mappa asincrona?
nerdizzle

@nerdizzle Sembra un buon candidato per un'altra domanda. In sostanza, tuttavia, con awaitla funzione attenderà il completamento (o il fallimento) dell'operazione asincrona prima di continuare, altrimenti continuerà immediatamente senza attendere.
Ajedi32

@ Ajedi32 grazie per la risposta. Ma senza l'attesa nella mappa asincrona non è più possibile attendere il risultato della funzione?
Nerdizzle

16

C'è un'altra soluzione per questo se non stai usando promesse native ma Bluebird.

Puoi anche provare a usare Promise.map () , mescolando array.map e Promise.all

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

2
È diverso: non esegue tutte le operazioni in parallelo, ma le esegue in sequenza.
Andrey Tserkus,

5
@AndreyTserkus Promise.mapSerieso Promise.eachsono sequenziali, Promise.mapli avvia tutti in una volta.
Kiechlus,

1
@AndreyTserkus è possibile eseguire tutte o alcune operazioni in parallelo fornendo l' concurrencyopzione.

11
Vale la pena ricordare che non è un JS alla vaniglia.
Michal,

@Michal sì, è SyntaxError
CS QGB il


2

Consiglierei di usare Promise.all come menzionato sopra, ma se hai davvero voglia di evitare quell'approccio, puoi fare un for o qualsiasi altro ciclo:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}

6
Promise.all sarà asincrono per ciascun elemento dell'array. Questa sarà una sincronizzazione, deve aspettare per finire un elemento per iniziare il successivo.
Santiago Mendoza Ramirez,

Per coloro che provano questo approccio, nota che for..of è il modo corretto di iterare il contenuto di un array, mentre for..in scorre sugli indici.
ralfoide,

2

Soluzione di seguito per elaborare tutti gli elementi di un array in modo asincrono E preservare l'ordine:

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

Anche codepen .

Si noti che "aspettiamo" solo per Promise.all. Chiamiamo calc senza "attendere" più volte e raccogliamo subito una serie di promesse irrisolte. Quindi Promise.all attende la risoluzione di tutti e restituisce un array con i valori risolti in ordine.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.