Utilice async aguarde con Array.map

171

Dado el siguiente 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 produce el siguiente error:

TS2322: El tipo 'Promesa <número> []' no se puede asignar al tipo 'número []'. El tipo 'Promesa <número> no se puede asignar al tipo' número '.

¿Cómo puedo arreglarlo? ¿Cómo puedo hacer async awaity Array.maptrabajar juntos?

solo
fuente
66
¿Por qué estás tratando de hacer una operación sincrónica en una operación asíncrona? arr.map()es sincrónico y no devuelve una promesa.
jfriend00
2
No puede enviar una operación asincrónica a una función, como map, que espera una sincrónica, y espera que funcione.
Heretic Monkey
1
@ jfriend00 Tengo muchas declaraciones en espera en la función interna. En realidad es una función larga y simplemente la simplifiqué para que sea legible. Ahora agregué una llamada en espera para aclarar por qué debería ser asíncrono.
Alon
Debe esperar algo que devuelva una promesa, no algo que devuelva una matriz.
jfriend00
2
Una cosa útil a tener en cuenta es que cada vez que marca una función como async, está haciendo que esa función devuelva una promesa. Entonces, por supuesto, un mapa asíncrono devuelve una serie de promesas :)
Anthony Manning-Franklin

Respuestas:

382

El problema aquí es que está tratando de hacer awaituna serie de promesas en lugar de una promesa. Esto no hace lo que esperas.

Cuando el objeto pasado awaitno es una Promesa, awaitsimplemente devuelve el valor tal como está inmediatamente en lugar de intentar resolverlo. Entonces, dado que pasó awaituna matriz (de objetos Promise) aquí en lugar de una Promesa, el valor devuelto por wait es simplemente esa matriz, que es de tipo Promise<number>[].

Lo que debe hacer aquí es llamar Promise.alla la matriz devuelta mappara convertirla en una sola Promesa antes de awaitiniciarla.

De acuerdo con los documentos de MDN paraPromise.all :

El Promise.all(iterable)método devuelve una promesa que se resuelve cuando se han resuelto todas las promesas del argumento iterable, o se rechaza con el motivo de la primera promesa aprobada que rechaza.

Entonces en tu 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;
}));

Esto resolverá el error específico que encuentre aquí.

Ajedi32
fuente
1
¿Qué :significan los dos puntos?
Daniel dice reinstalar a Mónica el
12
@DanielPendergast Es para anotaciones de tipo en TypeScript.
Ajedi32
¿Cuál es la diferencia entre llamar callAsynchronousOperation(item);con y sin awaitdentro de la función de mapa asíncrono?
nerdizzle
@nerdizzle Eso suena como un buen candidato para otra pregunta. Básicamente, sin embargo, con awaitla función esperará a que la operación asincrónica se complete (o falle) antes de continuar, de lo contrario, continuará inmediatamente sin esperar.
Ajedi32
@ Ajedi32 thx por la respuesta. Pero sin la espera en el mapa asincrónico, ¿ya no es posible esperar el resultado de la función?
nerdizzle
16

Hay otra solución si no está utilizando Promises nativas sino Bluebird.

También puede intentar usar Promise.map () , mezclando el array.map y Promise.all

En tu 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
fuente
2
Es diferente: no ejecuta todas las operaciones en paralelo, sino que las ejecuta en secuencia.
Andrey Tserkus
55
@AndreyTserkus Promise.mapSerieso Promise.eachson secuenciales, los Promise.mapinicia todos a la vez.
Kiechlus
1
@AndreyTserkus puede ejecutar todas o algunas operaciones en paralelo al proporcionar la concurrencyopción.
11
Vale la pena mencionar que no es un JS de vainilla.
Michal
@Michal sí, es SyntaxError
CS QGB
7

Si asigna una matriz de Promesas, puede resolverlas todas en una matriz de números. Ver Promesa.todos .

Dan Beaulieu
fuente
2

Recomiendo usar Promise.all como se mencionó anteriormente, pero si realmente desea evitar ese enfoque, puede hacer un bucle a favor o en cualquier otro:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}
Beckster
fuente
66
Promise.all será asíncrono para cada elemento de la matriz. Esta será una sincronización, tiene que esperar para terminar un elemento para comenzar el siguiente.
Santiago Mendoza Ramírez
Para aquellos que intentan este enfoque, tenga en cuenta que for..of es la forma correcta de iterar un contenido de matriz, mientras que for..in itera sobre los índices.
ralfoide
2

Solución a continuación para procesar todos los elementos de una matriz de forma asíncrona Y preservar el orden:

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

También codepen .

Note que solo "esperamos" por Promise.all. Llamamos calc sin "esperar" varias veces, y recopilamos una serie de promesas no resueltas de inmediato. Luego, Promise.all espera la resolución de todos ellos y devuelve una matriz con los valores resueltos en orden.

Miki
fuente