如何连续制作一长串 http 调用?
How do I make a long list of http calls in serial?
我试图一次只进行一个 http 调用,但是当我记录来自 getUrl
的响应时,它们堆积如山,我开始收到 409(请求太多)
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://whosebug.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));
}
这将立即用一堆 Promise
填充 mapped
,这不是您想要的。以下是您想要的:
let mapped;
for (u of urls) {
mapped.push(await doFetch(u));
}
但这不是 array.map()
所做的。因此,使用显式 for
循环是必要的。
我试图一次只进行一个 http 调用,但是当我记录来自 getUrl
的响应时,它们堆积如山,我开始收到 409(请求太多)
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://whosebug.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));
}
这将立即用一堆 Promise
填充 mapped
,这不是您想要的。以下是您想要的:
let mapped;
for (u of urls) {
mapped.push(await doFetch(u));
}
但这不是 array.map()
所做的。因此,使用显式 for
循环是必要的。