英文:
Why does Promise.catch not catch the error?
问题
I've written a retry function. But when fn
returns an error, the .catch
cannot output the error and execute attempt()
.
I call it by using
const res = await retry(fetch, url) as Response;
The console in the browser outputs 429 (Too Many Requests. Please try later)
but this is not from my code.
export function retry(fn: Function, params: any, times = 1e9 + 7) {
return new Promise((resolve, reject) => {
const attempt = () => {
fn(params).then(resolve).catch((err: Error) => {
console.log(err);
times-- > 0 ? attempt() : reject("fail");
})
}
attempt();
})
}
英文:
I've written a retry function. But when fn returns error, the .catch
cannot output the error and execute attempt().
I call it by using
const res = await retry(fetch, url) as Response;
The console in browser outputs 429 (Too Many Requests. Please try later)
but this is not from my code.
export function retry(fn: Function, params: any, times = 1e9 + 7) {
return new Promise((resolve, reject) => {
const attempt = () => {
fn(params).then(resolve).catch((err: Error) => {
console.log(err);
times-- > 0 ? attempt() : reject("fail");
})
}
attempt();
})
}
答案1
得分: 2
如果在日志中出现错误,但没有进入到您的.catch()
处理程序中,那么唯一的解释是您调用的fn()
函数没有返回一个在出现错误时被拒绝的承诺。因此,进一步调试此特定问题需要在引发此问题的特定fn
函数中进行。
特定的429错误是一种速率限制类型的错误,可能是因为您太快地进行了太多请求。根据您的代码,如果请求因某种持续性原因而失败,那么很容易发生这种情况,因此反复使用重试请求只会以相同的方式失败,直到目标主机最终表示您请求太快。
几乎所有生产质量的重试系统都会执行计时器退避(每个请求等待比上一个请求更长的时间),以便您不会连续不断地进行重试请求。相反,您为主机提供一些时间来恢复或可能解决导致错误的问题,并且希望还能避免速率限制的限制。客户端没有带有退避的快速重试会导致主机发生所谓的"雪崩故障"。雪崩故障是指服务器上的一个小问题导致许多客户端进入大规模重试循环,不断地向主机发送请求,以至于主机无法处理所有传入的请求,也无法从最初的问题中恢复过来。客户端中的适当退避可以防止这种情况发生。主机可能还会尝试通过速率限制来保护自己(您可能正在遇到这个问题)。重复在目标主机的速率限制之上以更快的速度发送相同的请求对您没有任何好处。
最后,您可以通过删除new Promise()
来清理代码,因为您可以直接从连续的调用中链接承诺并返回该承诺链。以下是具有该清理和简单回退算法的实现。
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
const kMinRetryTime = 100;
const kPerRetryAdditionalTime = 500;
function calcBackoff(retries) {
return Math.max(kMinRetryTime, (retries - 1) * kPerRetryAdditionalTime);
}
export function retry(fn: Function, params: any, times = 1e9 + 7) {
let retries = 0;
function attempt() {
return fn(params).catch((err: Error) => {
++retries;
console.log(err);
if (retries <= times) {
// still more retries left
return delay(calcBackoff(retries)).then(attempt);
} else {
// bail with error
throw err;
}
})
}
return attempt();
}
英文:
If you're getting an error in the logs, but it's not going into your .catch()
handler, then the only explanation for that is that whatever fn()
you're calling is not returning a promise that gets rejected when that error happens. So, further debugging on that specific issue would need to take place in the specific fn
function that causes this problem.
The specific 429 error is a rate limiting type error where you're likely making too many requests too quickly. Given your code, this could easily happen if the request fails for some persistent reason, so banging on it over and over with retry requests just continues to fail the same way until eventually the target host says you're making too many requests too quickly.
Nearly all production-quality retry systems that are going to do many consecutive retries will implement a timer backoff (each request waiting longer than the previous one before trying) so you don't just hammer retry requests one after another rapid fire. Instead, you provide the host some time to recover or perhaps remedy whatever the issue is that's causing the error and hopefully also avoid rate limiting restrictions. Having any client do rapid fire retries with no backoff is a recipe for what's known as an avalanche failure on the host. An avalanche failure is where one small problem on the server causes lots of clients to go into massive retry loops just hammering the host so hard that it can't handle all the incoming requests and can't recover from whatever the original problem was. An appropriate backoff in the client prevents this. Hosts may also try to protect themselves with rate limiting (which you are perhaps running into). There is also no benefit to you repeating the same request over and over faster than the target host's rate limit.
Then, lastly, you can clean up your code by removing the new Promise()
as you can just chain promises from successive calls and return that promise chain. Here's an implementation with that cleanup and a simple backoff algorithm.
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
const kMinRetryTime = 100;
const kPerRetryAdditionalTime = 500;
function calcBackoff(retries) {
return Math.max(kMinRetryTime, (retries - 1) * kPerRetryAdditionalTime);
}
export function retry(fn: Function, params: any, times = 1e9 + 7) {
let retries = 0;
function attempt() {
return fn(params).catch((err: Error) => {
++retries;
console.log(err);
if (retries <= times) {
// still more retries left
return delay(calcBackoff(retries)).then(attempt);
} else {
// bail with error
throw err;
}
})
}
return attempt();
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论