如何连续制作一长串 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,因此在任何地方使用它都比使用令人困惑的 .thenreduce 要容易得多。避免 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 循环是必要的。