Javascript 节点,等待并重试失败的api调用

Javascript 节点,等待并重试失败的api调用,javascript,node.js,es6-promise,Javascript,Node.js,Es6 Promise,因此,我从api获取一个URL数组,该数组有一个速率限制,目前我通过向每个调用添加一个超时来处理它,如下所示: const calls = urls.map((url, i) => new Promise(resolve => setTimeout(resolve, 250 * i)) .then(() => fetch(url) ) ); const data = await Promise.all(calls); 在每次通话之间强制等待250毫秒。这确保

因此,我
从api获取一个URL数组,该数组有一个速率限制,目前我通过向每个调用添加一个超时来处理它,如下所示:

const calls = urls.map((url, i) =>
  new Promise(resolve => setTimeout(resolve, 250 * i))
    .then(() => fetch(url)
  )
);

const data = await Promise.all(calls);
在每次通话之间强制等待250毫秒。这确保了永远不会超过费率限制


问题是,这不是真的必要。我尝试了0毫秒的等待时间,在大多数情况下,在api开始返回之前,我必须反复重新加载页面四到五次:

{错误:{状态:429,消息:'API速率限制已超出'}}

大多数情况下,您只需等待一秒钟左右,就可以安全地重新加载页面并获取所有数据

一种更合理的方法是收集返回429的调用(如果返回429的话),等待一定的时间,然后重试(可能会重复一定的次数)

问题是,我有点困惑,如何才能实现这一目标

编辑:

刚到家,我会仔细查看答案,但似乎有一个假设,我认为这是不必要的:电话不必是连续的,可以按任何顺序发出(和返回)。

你想要的是指数退避。您可以修改代码,使其在特定故障条件下继续尝试:

const max_wait = 2000;

async function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

const calls = urls.map(async (url) => {
  let retry = 0, result;

  do {
    if (retry !== 0) { await wait(Math.pow(2, retry); }
    result = await fetch(url);
    retry++;
  } while(result.status !== 429 || (Math.pow(2, retry) > max_wait))

  return result;
}

或者,您可以尝试使用库来处理回退,例如,通过设置以毫秒为单位的延迟,并接受第三次回调,确定是否应重试请求,从而允许依次处理一系列承诺

在下面的代码中,一些示例请求被模拟为:

  • 测试成功的响应
  • 测试错误响应。如果错误响应包含错误代码且错误代码为403,则返回
    true
    ,并在下一次运行中重试调用(延迟x毫秒)
  • 在没有错误代码的情况下测试错误响应
下面有一个全局计数器,它在N次尝试后放弃承诺(在下面的示例5中),所有这些都在这段代码中处理:

const result = await resolveSequencially(promiseTests, 250, (err) => {
    return ++errorCount, !!(err && err.error && err.error.status === 403 && errorCount <= 5);
  });
const result=依次等待解析(promiseTests,250,(err)=>{
return++errorCount,!!(err&&err.error&&err.error.status==403&&errorCount{
log('捕获到一个错误。调用回调以检查是否应该重试!错误为:',err);
//否则,调用onFailed回调。
const retry=onFailed(err);
//如果retry为true,则使用相同的索引再次调用递归函数。
log('retry is',retry);
如果(重试)递归句柄(当前,最大);

else recursiveHandle(current+1,max);//{handlePromise()},delay);//如果我正确理解这个问题,您可以尝试:

a) 按顺序执行
fetch()
调用(可能有可选延迟)

b) 重试失败的请求,但有退避延迟

正如您可能发现的那样,
.map()
对a)并没有真正的帮助,因为它在迭代时不等待任何异步内容(这就是为什么您使用
i*250
创建越来越大的超时)

我个人认为,通过使用
for of
循环来保持事情的顺序是最容易的,因为这将很好地与
async/await
配合使用:

const fetchQueue=async(URL,延迟=0,重试次数=0,最大重试次数=3)=>{
常量等待=(超时=0)=>{
if(timeout){console.log(`Waiting for${timeout}`);}
返回新承诺(解决=>{
setTimeout(解析,超时);
});
};
for(url的url){
试一试{
等待等待(重试次数?重试次数*Math.max(延迟,1000):延迟);
let response=等待获取(url);
让数据=等待(
response.headers.get('content-type')。包括('json')
?response.json()
:response.text()
);
答复={
标题:[…响应.标题].reduce((acc,标题)=>{
返回{…acc[header[0]]:header[1]};
}, {}),
状态:response.status,
数据:数据,
};
//事实上,只有在出现错误时才这样做
//这对重试是有意义的
if([404429].includes(response.status)){
抛出新错误(`Status Code${response.Status}`);
}
console.log(response.data);
}捕捉(错误){
console.log('Error:',err.message);
如果(重试次数<最大重试次数){
log(`Retry}${retries+1}${url}`);
等待获取队列([url],延迟,重试次数+1,最大重试次数);
}否则{
log(`Max retries reated for${url}`);
}
}
}
};
//填充一些真实的URL以获取
//索引0将生成不存在的URL以测试错误行为
constURL=新数组(101).fill(null).map((x,i)=>`https://jsonplaceholder.typicode.com/todos/${i}`);
//一个接一个地获取URL(按顺序)
//并将每个请求延迟250毫秒

获取队列(URL,250)
在每次调用之间强制250毫秒的等待实际上,这并没有发生,因为您正在调用
Promise.all
,它运行所有承诺并以250毫秒的延迟解决每个承诺。您没有延迟调用,也没有依次执行它们。@briosheje-实际上,超时设置为url的索引*250(因此第二个等待500毫秒,以此类推)侧注@briosheje提到的内容:允许并发,在本用例中可能很有用。如果您可以从客户端调用api,通常没有速率限制。@Justiniessner不能保证前一次调用已经完成,api限制通常提到您不应执行并行调用。如果一次调用需要1或2秒,则更多的p将执行并行调用。我一到家就会查看这些调用,但这些调用不必是连续的,它们可以按任何顺序触发(和返回)。使用上述函数,不会发生这种情况,因为它总是等待下一个请求,直到挂起的一个请求完成,但这样您可能会避免错误