javascript - 如何实现 Promise 重试和撤消
问题描述
我很好奇应该如何实现 API 重试和超时。有时仅仅等待一个 api 调用然后捕获出现的任何错误是不够的。如果我需要发出一连串异步请求,如下所示:
await client
.callA()
.then(async () => await callB())
.then(async () => await callC())
.catch(err => console.error(err));
如果其中一个承诺在中链失败,我想在几秒钟后再次尝试请求,直到尝试用完。
这是我尝试制作重试包装器。
async function retry (fn, undo, attempts, wait = 5000) {
await fn().catch(async (err) => {
console.error(err.message + `\n retrying in ${wait/1000} seconds...`);
if (attempts !== 0) {
// async timeout
await new Promise((resolve) => {
setTimeout(() => resolve(retry(fn, undo, attempts - 1)), wait);
})
} else {
await undo()
}
})
}
await retry(calls, undoCalls, 10)
callA -> callB -> callC
说callA()
成功,但callB()
失败,我希望包装器callB()
每隔一段时间重试一次,而不是重新开始。然后:
callB()
最终在允许的尝试范围内成功,继续前进callC()
。callB()
尝试用完,调用undoCallA()
以恢复之前所做的更改。
重复上述直到链结束。
我想了解一下这是如何实现的,或者是否有一个库可以做类似的事情。谢谢!
解决方案
函数应该很简单,只做一件事。我会从一个通用的开始sleep
-
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
使用简单的函数,我们可以构建更复杂的函数,比如timeout
-
const timeout = (p, ms) =>
Promise.race([ p, sleep(ms).then(_ => { throw Error("timeout") }) ])
现在假设我们有一个任务,myTask
最多需要 4 秒才能运行。如果生成奇数则成功返回。否则它会拒绝,“X 不是奇数” ——
async function myTask () {
await sleep(Math.random() * 4000)
const x = Math.floor(Math.random() * 100)
if (x % 2 == 0) throw Error(`${x} is not odd`)
return x
}
现在假设我们要运行myTask
两timeout
(2) 秒且retry
最多三 (3) 次 -
retry(_ => timeout(myTask(), 2000), 3)
.then(console.log, console.error)
Error: 48 is not odd (retry 1/3)
Error: timeout (retry 2/3)
79
第一次尝试可能myTask
会产生奇数。或者它可能会在发出最终错误之前用尽所有尝试 -
Error: timeout (retry 1/3)
Error: timeout (retry 2/3)
Error: 34 is not odd (retry 3/3)
Error: timeout
Error: failed after 3 retries
现在我们实现retry
. 我们可以使用一个简单的for
循环 -
async function retry (f, count = 5, ms = 1000) {
for (let attempt = 1; attempt <= count; attempt++) {
try {
return await f()
}
catch (err) {
if (attempt <= count) {
console.error(err.message, `(retry ${attempt}/${count})`)
await sleep(ms)
}
else {
console.error(err.message)
}
}
}
throw Error(`failed after ${count} retries`)
}
现在我们了解了如何retry
工作,让我们编写一个更复杂的示例来重试多个任务 -
async function pick3 () {
const a = await retry(_ => timeout(myTask(), 3000))
console.log("first pick:", a)
const b = await retry(_ => timeout(myTask(), 3000))
console.log("second pick:", b)
const c = await retry(_ => timeout(myTask(), 3000))
console.log("third pick:", c)
return [a, b, c]
}
pick3()
.then(JSON.stringify)
.then(console.log, console.error)
Error: timeout (retry 1/5)
Error: timeout (retry 2/5)
first pick: 37
Error: 16 is not odd (retry 1/5)
second pick: 13
Error: 60 is not odd (retry 1/5)
Error: timeout (retry 2/5)
third pick: 15
[37,13,15]
展开下面的代码片段以在您的浏览器中验证结果 -
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
const timeout = (p, ms) =>
Promise.race([ p, sleep(ms).then(_ => { throw Error("timeout") }) ])
async function retry (f, count = 5, ms = 1000) {
for (let attempt = 0; attempt <= count; attempt++) {
try {
return await f()
}
catch (err) {
if (attempt < count) {
console.error(err.message, `(retry ${attempt + 1}/${count})`)
await sleep(ms)
}
else {
console.error(err.message)
}
}
}
throw Error(`failed after ${count} retries`)
}
async function myTask () {
await sleep(Math.random() * 4000)
const x = Math.floor(Math.random() * 100)
if (x % 2 == 0) throw Error(`${x} is not odd`)
return x
}
async function pick3 () {
const a = await retry(_ => timeout(myTask(), 3000))
console.log("first", a)
const b = await retry(_ => timeout(myTask(), 3000))
console.log("second", b)
const c = await retry(_ => timeout(myTask(), 3000))
console.log("third", c)
return [a, b, c]
}
pick3()
.then(JSON.stringify)
.then(console.log, console.error)
并且由于timeout
与 解耦retry
,我们可以实现不同的程序语义。相比之下,以下示例不会使单个任务超时,但如果myTask
返回偶数会重试 -
async function pick3 () {
const a = await retry(myTask)
const b = await retry(myTask)
const c = await retry(myTask)
return [a, b, c]
}
我们现在可以说它timeout
pick3
是否需要超过十 (10) 秒,retry
如果需要,整个选择 -
retry(_ => timeout(pick3(), 10000))
.then(JSON.stringify)
.then(console.log, console.error)
这种以多种方式组合简单功能的能力使它们比一个试图独自完成所有事情的大型复杂功能更强大。
当然,这意味着我们可以retry
直接应用于您问题中的示例代码 -
async function main () {
await retry(callA, ...)
await retry(callB, ...)
await retry(callC, ...)
return "done"
}
main().then(console.log, console.error)
您可以申请timeout
个人电话 -
async function main () {
await retry(_ => timeout(callA(), 3000), ...)
await retry(_ => timeout(callB(), 3000), ...)
await retry(_ => timeout(callC(), 3000), ...)
return "done"
}
main().then(console.log, console.error)
或适用timeout
于每个retry
-
async function main () {
await timeout(retry(callA, ...), 10000)
await timeout(retry(callB, ...), 10000)
await timeout(retry(callC, ...), 10000)
return "done"
}
main().then(console.log, console.error)
或者可能适用timeout
于整个过程 -
async function main () {
await retry(callA, ...)
await retry(callB, ...)
await retry(callC, ...)
return "done"
}
timeout(main(), 30000).then(console.log, console.error)
或任何其他符合您实际意图的组合!
推荐阅读
- gatsby - 在 Gatsby 中为 IE11 启用自动前缀(特别是)网格
- excel - 从另一个工作簿更新数据透视表工作簿中的数据
- postgresql - Symfony 5 和 PostgreSQL 连接
- sql - Laravel:按月和年排序
- regex - 正则表达式仅在等于 / 时删除 URL 字符串中的最后一个字符
- android - 如何更改arcore sceneform中所选模型的颜色?我正在使用颜色选择器
- c# - Unity Xcode LowLevelCullingLoops.cpp 崩溃
- python - 在 matplotib 和 geopandas 中注释 xy 点
- neovim - NeoVim:在 :terminal 中运行命令后移动光标
- css - sass mixin 将参数作为 css 变量传递