使用 Array.reduce() 从提取承诺中提取 JSON 数据

Extracting JSON data from fetch promises using Array.reduce()

我正在编写一个 then() 语句,用于从 fetch() 的响应数组中提取 json 数据。在下面的代码中 queries 是一系列调用 fetch() 返回的承诺数组。我正在使用 async/await 作为响应,因为否则承诺将在没有解决的情况下返回(我在 中找到了解决方案)。

我的第一次尝试工作正常,当我进入 jsonified 时,我获得了一个以 promises 作为元素的数组:

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() 时,我意识到当我推入累加器而不是获取一个以 promise 作为元素的数组时,acc 被 分配为取而代之的是承诺.

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

我可以毫无问题地使用第一个版本,程序运行正常,但是 Array.reduce() 里面发生了什么? 为什么要将承诺推入累加器 returns 承诺而不是数组? 我如何使用 Array.reduce() 重构代码?

让累加器的初始值成为解析为空数组的 Promise,然后 await 每次迭代的累加器(以便所有先前的迭代在当前迭代运行之前解析)

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

(也就是说,你的原始代码更清晰,我更喜欢 .reduce 版本)

现场演示:

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并行调用每个Promise的.json(),以便更快地产生结果:

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

如果查询是刚刚从 fetch 生成的 Response 数组,将 .json() 调用链接到原始 fetch 会更好] 改为调用,例如:

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

这样,您可以在响应返回时立即使用响应,而不必等待所有响应返回在开始处理第一个之前。

虽然这不是您所要求的,但您可以避免必须使用 reduce 的痛苦,而只需利用您已经在使用的 Promise.all()

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

这是一种更短的方式,当您回过头来阅读它时也不那么头疼。