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

IT技术 javascript promise es6-promise fetch-api
2021-01-31 07:56:26

如果我有一组网址:

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在这里使用正确的方法吗?

6个回答

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

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

您当前的代码不起作用,因为不forEach返回任何内容(既不是数组也不是Promise)。

当然,您可以简化它,并在相应的 fetch Promise完成后立即从每个响应中获取主体:

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 是一种奇怪的语言
2021-03-15 07:56:26
@sansSpoon 您确定您使用了我在回答中使用的简洁箭头主体吗?确实隐式地返回了Promise。
2021-03-22 07:56:26
嗯,当然,手掌>脸。我总是混合我的 es5/6。谢谢。
2021-04-01 07:56:26
解决我在问题中感觉到的一些问题:由于异步在 JavaScript 中的工作方式,您不能将结果“提取”到外部变量,但您可以使用生成器或 async/await 来模拟它。有关JS 中异步性的完整指南,请参阅此答案
2021-04-05 07:56:26
@1252748 是的,Promise.all()返回一个数组或响应对象的Promise。但随后,responses.map(res => res.text())产生另一种阵列Promise的其中需要第二次Promise.all
2021-04-05 07:56:26

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

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

var promises = urls.map(url => fetch(url).then(y => y.text()));
Promise.all(promises).then(results => {
    // do something with results.
});
您的(/ Benjamin Gruenbaum 的)答案与 Bergi 的第二个片段之间确实没有区别。— 假设我将您的第二个替换promisesurls.map(url => fetch(url).then(y => y.text())),然后将您的替换yresp,最后将您的替换resultstexts与 Bergi 的第二个片段唯一的不同之处在于注释// do something with results行的位置(好吧——还有最后的分号)。(这可以解释为什么 Benjamin Gruenbaum 删除了他的答案吗?)
2021-04-05 07:56:26
谢谢,我已经查看了几个关于仅在Promise的任务实际完成后才做某事的 SO 答案。这个答案实际上是在触发我的回调之前等待所有文件完成获取。
2021-04-11 07:56:26

你应该使用map而不是forEach

Promise.all(urls.map(url => fetch(url)))
.then(resp => Promise.all( resp.map(r => r.text()) ))
.then(result => {
    // ...
});
为什么使用 map 而不是 forEach?只是想问?
2021-03-15 07:56:26
@ChristianMatthewmap返回fetch(url)while 的每个结果forEach不返回任何内容。请参阅已接受的答案或stackoverflow.com/a/34426481/1639983
2021-03-23 07:56:26
谢谢@HoldenLewis 这是我想到的答案
2021-04-09 07:56:26

建议的数组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)与其合并:

namesonly = ['two', 'three']

所需的输出是一组对象:

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

我已将属性名称更改titleloremipsum.

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

参考

以防万一,如果您使用的是 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 => {})  
可以通过其他支持信息来改进您的答案。编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何编写好的答案的更多信息
2021-03-26 07:56:26