我试图一次只打一次http呼叫,但是当我记录来自getUrl
的响应时,它们正在堆积,我开始收到409s(请求太多)
function getUrl(url, i, cb) {
const fetchUrl = `https://api.scraperapi.com?api_key=xxx&url=${url.url}`;
fetch(fetchUrl).then(async res => {
console.log(fetchUrl, 'fetched!');
if (!res.ok) {
const err = await res.text();
throw err.message || res.statusText;
}
url.data = await res.text();
cb(url);
});
}
let requests = urls.map((url, i) => {
return new Promise(resolve => {
getUrl(url, i, resolve);
});
});
const all = await requests.reduce((promiseChain, currentTask) => {
return promiseChain.then(chainResults =>
currentTask.then(currentResult => [...chainResults, currentResult]),
);
}, Promise.resolve([]));
基本上,我不希望下一个http开始,直到前一个http完成。否则,我会锤击他们的服务器。
奖励要点:每次并行处理5次。
由于您正在使用await
,所以将其在任何地方使用都比将.then
与reduce
混淆要容易得多。避免使用explicit Promise construction antipattern也很不错。这应该做您想要的:
const results = [];
for (const url of urls) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(response); // or whatever logic you need with errors
}
results.push(await response.text());
}
然后您的results
变量将包含响应文本数组(否则将引发错误,并且代码将无法到达底部)。
async
函数的语法是参数列表之前的async
关键字,就像您在原始代码中所做的一样:
const fn = async () => {
const results = [];
for (const url of urls) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(response); // or whatever logic you need with errors
}
results.push(await response.text());
}
// do something with results
};
要一次只处理有限数量的请求,请创建一个队列系统-请求完成后,递归调用另一个请求的函数,例如:
const results = [];
const queueNext = async () => {
if (!urls.length) return;
const url = urls.shift();
const response = await fetch(url);
if (!response.ok) {
throw new Error(response); // or whatever logic you need with errors
}
results.push(await response.text());
await queueNext();
}
await Promise.all(Array.from({ length: 5 }, queueNext));
// do something with results
您不能使用数组方法顺序运行异步操作,因为数组方法都是同步的。
实现顺序异步任务的最简单方法是通过循环。否则,您将需要编写一个自定义函数来模仿循环并在异步任务结束后运行.then
,这非常麻烦且不必要。
而且,fetch
已经返回了一个Promise,所以您不必自己创建一个Promise即可包含fetch
返回的那个Promise。
下面的代码是一个有效的示例,对原始代码进行了一些小的更改(请参见注释)。
// Fake urls for example purpose
const urls = [{ url: 'abc' }, { url: 'def', }, { url: 'ghi' }];
// To imitate actual fetching
const fetch = (url) => new Promise(resolve => {
setTimeout(() => {
resolve({
ok: true,
text: () => new Promise(res => setTimeout(() => res(url), 500))
});
}, 1000);
});
function getUrl(url, i, cb) {
const fetchUrl = `https://api.scraperapi.com?api_key=xxx&url=${url.url}`;
return fetch(fetchUrl).then(async res => { // <-- changes here
console.log(fetchUrl, 'fetched!');
if (!res.ok) {
const err = await res.text();
throw err.message || res.statusText;
}
url.data = await res.text();
return url; // <--- changes here
});
}
async function getAllUrls(urls){
const result = [];
for (const url of urls){
const response = await getUrl(url);
result.push(response);
}
return result;
}
getAllUrls(urls)
.then(console.log);
async/await
非常适合此。
假设您有一组URL作为字符串:
let urls = ["https://example.org/", "https://google.com/", "https://stackoverflow.com/"];
您只需要做:
for (let u of urls) {
await fetch(u).then(res => {
// Handle response
}).catch(e => {
// Handle error
});
}
直到当前的fetch()
解析完,循环才会迭代,这将序列化事物。
array.map
不起作用的原因如下:
async function doFetch(url) {
return await fetch(url).then(res => {
// Handle response
}).catch(e => {
// Handle error
});
}
let mapped = urls.map(doFetch);
等效于:
let mapped;
for (u of urls) {
mapped.push(doFetch(u));
}
这将立即用一堆mapped
填充Promise
,这不是您想要的。以下是您想要的:
let mapped;
for (u of urls) {
mapped.push(await doFetch(u));
}
但是这不是array.map()
所做的。因此,必须使用显式的for
循环。