如何使用 Promise.all 获取 URL 数组?

Posted

技术标签:

【中文标题】如何使用 Promise.all 获取 URL 数组?【英文标题】:How can I fetch an array of URLs with Promise.all? 【发布时间】:2015-10-21 01:02:05 【问题描述】:

如果我有一个 url 数组:

var urls = ['1.txt', '2.txt', '3.txt']; // these text files contain "one", "two", "three", respectively.

我想构建一个如下所示的对象:

var text = ['one', 'two', 'three'];

我一直在尝试使用 fetch 来学习如何做到这一点,当然它会返回 Promises。

我尝试过的一些事情不起作用

var promises = urls.map(url => fetch(url));
var texts = [];
Promise.all(promises)
  .then(results => 
     results.forEach(result => result.text()).then(t => texts.push(t))
  )

这看起来不对,而且无论如何它都行不通——我没有得到一个数组 ['one', 'two', 'three']。

在这里使用Promise.all 是正确的方法吗?

【问题讨论】:

这看起来像是一个包围错误。你真的打算在.forEach(…) 的返回值上调用.then,还是在….text() 上调用? 你在哪里查看/记录texts并观察它仍然是空的? 【参考方案1】:

是的,Promise.all 是正确的方法,但是如果您想首先 fetch 所有 url,然后从它们那里获取所有 texts(这也是对响应正文的承诺),您实际上需要两次)。所以你需要这样做

Promise.all(urls.map(u=>fetch(u))).then(responses =>
    Promise.all(responses.map(res => res.text()))
).then(texts => 
    …
)

您当前的代码不起作用,因为 forEach 什么都不返回(既不是数组也不是承诺)。

当然,您可以简化它,并在相应的 fetch 承诺履行后立即从每个响应中获取正文:

Promise.all(urls.map(url =>
    fetch(url).then(resp => resp.text())
)).then(texts => 
    …
)

或与await 相同:

const texts = await Promise.all(urls.map(async url => 
  const resp = await fetch(url);
  return resp.text();
));

【讨论】:

解决我在问题中感觉到的问题:由于异步在 javascript 中的工作方式,您无法将结果“提取”到外部变量,但您可以使用生成器或 async/await 来模拟它。有关 JS 异步性的完整指南,请参阅 this answer。 这看起来太棒了!但我无法理解:( javascript 是一种奇怪的语言 @sansSpoon 你确定你使用了我在回答中的简洁箭头吗?它确实隐式返回了承诺。 Doh,当然是手掌> 脸。我一直在混合我的 es5/6。谢谢。 @1252748 是的,Promise.all() 为数组或响应对象返回单个承诺。但是随后,responses.map(res => res.text()) 生成另一个数组 of promises,它需要第二个 Promise.all【参考方案2】:

出于某种原因,Bergi 的两个例子都不适合我。它只会给我空的结果。经过一些调试后,promise 似乎会在 fetch 完成之前返回,因此结果为空。

然而,Benjamin Gruenbaum 早前在这里有一个答案,但将其删除。他的方法确实对我有用,所以我将在此处复制粘贴,作为替代方案,以防其他人在此处的第一个解决方案中遇到任何问题。

var promises = urls.map(url => fetch(url).then(y => y.text()));
Promise.all(promises).then(results => 
    // do something with results.
);

【讨论】:

谢谢,我查看了几个关于仅在承诺的任务实际完成后才做某事的 SO 答案。这个答案实际上是在触发我的回调之前等待所有文件完成获取。 您(/ Benjamin Gruenbaum 的)答案与 Bergi 的第二个 sn-p 之间确实没有区别。 —假设我用urls.map(url => fetch(url).then(y => y.text()))替换你的第二个promises,然后用resp替换你的y,最后用texts替换你的results。与 Bergi 的第二个 sn-p 唯一不同的是注释 // do something with results,以及换行的位置(嗯——以及最后一个分号)。 (这可以解释为什么 Benjamin Gruenbaum 删除了他的答案吗?)【参考方案3】:

您应该使用map 而不是forEach

Promise.all(urls.map(url => fetch(url)))
.then(resp => Promise.all( resp.map(r => r.text()) ))
.then(result => 
    // ...
);

【讨论】:

为什么使用 map 而不是 forEach?只是问问? @ChristianMatthew map 返回 fetch(url) 的每个结果,而 forEach 不返回任何内容。查看接受的答案或***.com/a/34426481/1639983。 感谢@HoldenLewis,这是我想到的答案【参考方案4】:

建议的数组urls = ['1.txt', '2.txt', '3.txt'] 没有多大作用 对我来说有意义,所以我将改为使用:

urls = ['https://jsonplaceholder.typicode.com/todos/2',
        'https://jsonplaceholder.typicode.com/todos/3']

两个 URL 的 JSON:

"userId":1,"id":2,"title":"quis ut nam facilis et officia qui",
 "completed":false
"userId":1,"id":3,"title":"fugiat veniam minus","completed":false

目标是得到一个对象数组,其中每个对象包含title 来自相应 URL 的值。

为了让它更有趣一点,我假设已经有一个 我希望 URL 结果数组(titles)的 names 数组 合并:

namesonly = ['two', 'three']

所需的输出是一个对象数组:

["name":"two","loremipsum":"quis ut nam facilis et officia qui",
"name":"three","loremipsum":"fugiat veniam minus"]

我已将属性名称 title 更改为 loremipsum

const namesonly = ['two', 'three'];
const urls = ['https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'];

Promise.all(urls.map(url => fetch(url)
  .then(response => response.json())
  .then(responseBody => responseBody.title)))
  .then(titles => 
    const names = namesonly.map(value => ( name: value ));
    console.log('names: ' + JSON.stringify(names));
    const fakeLatins = titles.map(value => ( loremipsum: value ));
    console.log('fakeLatins:\n' + JSON.stringify(fakeLatins));
    const result =
      names.map((item, i) => Object.assign(, item, fakeLatins[i]));
    console.log('result:\n' + JSON.stringify(result));
  )
  .catch(err => 
    console.error('Failed to fetch one or more of these URLs:');
    console.log(urls);
    console.error(err);
  );
.as-console-wrapper  max-height: 100% !important; top: 0; 

参考

Convert an array of values to an array of objects

【讨论】:

【参考方案5】:

以防万一,如果您使用的是 axios。我们可以这样实现:

const apiCall = (endpoint:string)=> axios.get($baseUrl/$endpoint)

axios.all([apiCall('https://first-endpoint'),apiCall('https://second-endpoint')]).then(response => 
            response.forEach(values => values)
            ).catch(error => )  

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。【参考方案6】:

这是一个干净的方法。

const requests = urls.map((url) => fetch(url)); 
const responses = await Promise.all(requests); 
const promises = responses.map((response) => response.text());
return await Promise.all(promises);

【讨论】:

以上是关于如何使用 Promise.all 获取 URL 数组?的主要内容,如果未能解决你的问题,请参考以下文章

怎样同时获取10000+接口的返回值:Promise.all高并发限制解决方案

怎样同时获取10000+接口的返回值:Promise.all高并发限制解决方案

如何返回 Promise.all 获取 api json 数据?

如何在 useEffect 挂钩中使用 Promise.all 获取所有数据?

如何使用带有 Promise.all 的 AsyncData 从多个 api 的工作客户端获取数据但导致 nginx 为 504

如何正确处理 Promise.all:未定义