如何使用 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
来学习如何做到这一点,当然它会返回 Promise
s。
我尝试过的一些事情不起作用:
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,然后从它们那里获取所有 text
s(这也是对响应正文的承诺),您实际上需要两次)。所以你需要这样做
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?只是问问? @ChristianMatthewmap
返回 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