Извлечение данных JSON из обещаний получения с помощью Array.reduce () - PullRequest
1 голос
/ 23 декабря 2019

Я пишу оператор then() для извлечения данных json из массива ответов из fetch(). В приведенном ниже коде queries представляет собой массив обещаний, возвращаемых серией вызовов fetch(). Я использую async / await для ответа, потому что в противном случае обещания были бы возвращены без разрешения (я нашел решение в этот вопрос ).

Моя первая попытка работала правильно, когда я нажимаюв jsonified я получаю массив с обещаниями в качестве элементов:

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

Но когда я пошел на рефакторинг и попытался использовать Array.reduce(), я понял, что когда я запихиваю аккумулятор вместо получения массивас обещанием в качестве элемента, назначено вместо обещания .

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

Я могу использовать первую версию без каких-либо проблем, и программа работает правильно, но что происходит внутри Array.reduce()? Почему вставка обещания в накопитель возвращает целое число обещания массива? Как я могу изменить код с помощью Array.reduce()?

Ответы [ 2 ]

3 голосов
/ 23 декабря 2019

Хотя это не то, что вы просили, вы можете избежать боли, связанной с использованием reduce, и просто использовать Promise.all(), который вы уже используете:

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

Это оченькороткий путь и меньше головной боли, чтобы читать, когда вы вернетесь к нему.

2 голосов
/ 23 декабря 2019

Пусть начальным значением аккумулятора является Обещание, которое разрешается в пустой массив, затем await аккумулятор на каждой итерации (чтобы все предыдущие итерации разрешались до выполнения текущей итерации)

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

(Тем не менее, ваш оригинальный код намного понятнее, я бы предпочел его * версии .reduce)

Live демо:

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

Если у вас нет для получения всех данных в последовательном порядке, рассмотрите возможность использования Promise.all для параллельного вызова .json() каждого Promise, поэтомучто результат будет получен быстрее:

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

Если запросы являются массивом Response s, которые были только что сгенерированы из fetch, было бы еще лучше связать вызов .json() свместо этого исходный fetch вызов, например:

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

Таким образом, вы можете потреблять ответы немедленно , когда они возвращаются, вместо того, чтобы ждать все ответов, которые нужно вернуть перед началом обработки первого.

...