Estoy escribiendo una declaración then() para extraer los datos json de una matriz de respuestas de fetch(). En el siguiente código queries hay una serie de promesas devueltas por una serie de llamadas a fetch(). Estoy usando async / await para la respuesta porque de lo contrario las promesas serían devueltas sin resolverse (encontré una solución en esta pregunta).

Mi primer intento funcionó correctamente, cuando presiono jsonified obtengo una matriz con las promesas como elementos:

return Promise.all(queries)
.then(async(responses)=> {
    let jsonified = [];
    for (let res of responses){
        jsonified.push(await(res.json()));
    }
    return jsonified;
}.then(data=> ...

Pero cuando fui a refactorizar e intenté usar Array.reduce(), me di cuenta de que cuando empujo al acumulador en lugar de obtener una matriz con una promesa como elemento, acc se asigna como una promesa en su lugar .

.then(responses=> {
    return responses.reduce(async(acc, next) => {
        acc.push(await(next.json()));
        return acc;
    }, [])
})

Puedo usar la primera versión sin ningún problema y el programa funciona correctamente, pero ¿qué sucede dentro de Array.reduce()? ¿Por qué insertar una promesa en el acumulador devuelve una promesa en lugar de una matriz? ¿Cómo podría refactorizar el código con Array.reduce()?

1
maja 23 dic. 2019 a las 11:25

2 respuestas

La mejor respuesta

Haga que el valor inicial del acumulador sea una Promesa que se resuelva en una matriz vacía, luego await el acumulador en cada iteración (para que todas las iteraciones anteriores se resuelvan antes de que se ejecute la iteración actual)

.then(responses=> {
    return responses.reduce(async (accPromiseFromLastIter, next) => {
       const arr = await accPromiseFromLastIter;
       arr.push(await next.json());
       return arr;
    }, Promise.resolve([]))
})

(Dicho esto, su código original es mucho más claro, lo preferiría a la versión .reduce)

Demo en vivo:

const makeProm = num => Promise.resolve(num * 2);

const result = [1, 2, 3].reduce(async(accPromiseFromLastIter, next) => {
  const arr = await accPromiseFromLastIter;
  arr.push(await makeProm(next));
  return arr;
}, Promise.resolve([]));

result.then(console.log);

A menos que tenga para recuperar todos los datos en serie, considere utilizar Promise.all para llamar al .json() de cada Promesa en paralelo, para que el resultado se produzca más rápidamente:

return Promise.all(queries)
.then(responses => Promise.all(responses.map(response => response.json())));

Si las consultas son una matriz de Response s que se acaban de generar a partir de fetch, sería aún mejor encadenar la llamada .json() en la llamada original fetch, por ejemplo :

const urls = [ ... ];
const results = await Promise.all(
  urls.map(url => fetch(url).then(res => res.json()))
);

De esta forma, puede consumir las respuestas inmediatamente cuando vuelvan, en lugar de tener que esperar a que todas vuelvan antes de comenzar a procesar la primera.

2
CertainPerformance 23 dic. 2019 a las 08:34

Aunque no es lo que ha pedido, puede evitar el dolor de tener que usar reduce, y simplemente utilizar el Promise.all() que ya está usando:

return Promise.all(queries.map(q => q.then(res => res.json()))
  .then(data => {...})

Es una forma mucho más corta y menos dolor de cabeza para leer cuando vuelves a ella.

3
Kobe 23 dic. 2019 a las 08:34