我如何制作一连串的HTTP通话清单?

问题描述 投票:1回答:3

我试图一次只打一次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次。

javascript node.js ecmascript-5 ecmascript-7
3个回答
1
投票

由于您正在使用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

0
投票

您不能使用数组方法顺序运行异步操作,因为数组方法都是同步的。

实现顺序异步任务的最简单方法是通过循环。否则,您将需要编写一个自定义函数来模仿循环并在异步任务结束后运行.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);

0
投票

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循环。

© www.soinside.com 2019 - 2024. All rights reserved.