Ter*_*rry 1 javascript asynchronous promise async-await typescript
我很好奇 API 重试和超时应该如何实现。有时,仅仅等待 api 调用然后捕获出现的任何错误是不够的。如果我需要发出一系列异步请求,如下所示:
await client
.callA()
.then(async () => await callB())
.then(async () => await callC())
.catch(err => console.error(err));
Run Code Online (Sandbox Code Playgroud)
如果其中一个承诺在中链失败,我想在几秒钟后再次尝试请求,直到尝试用完。
这是我尝试制作重试包装器。
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)
Run Code Online (Sandbox Code Playgroud)
callA -> callB -> callC
说callA()成功了,但callB()失败了,我希望包装器每隔一段时间重试callB()一次,而不是重新开始。然后:
callB()最终在允许的尝试范围内成功,继续前进callC()。callB()尝试次数用尽,调用undoCallA()以恢复之前所做的更改。重复上述操作直到链末端。
我想了解一下这是如何实现的,或者是否有一个库可以做类似的事情。谢谢!
函数应该简单并且只做一件事。我会从一个通用的开始sleep-
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
Run Code Online (Sandbox Code Playgroud)
使用简单的函数我们可以构建更复杂的函数,例如timeout-
const timeout = (p, ms) =>
Promise.race([ p, sleep(ms).then(_ => { throw Error("timeout") }) ])
Run Code Online (Sandbox Code Playgroud)
现在假设我们有一个任务,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
}
Run Code Online (Sandbox Code Playgroud)
现在假设我们想要运行myTask两timeout(2) 秒,retry最多三 (3) 次 -
retry(_ => timeout(myTask(), 2000), 3)
.then(console.log, console.error)
Run Code Online (Sandbox Code Playgroud)
Error: 48 is not odd (retry 1/3)
Error: timeout (retry 2/3)
79
Run Code Online (Sandbox Code Playgroud)
第一次尝试可能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
Run Code Online (Sandbox Code Playgroud)
现在我们实施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`)
}
Run Code Online (Sandbox Code Playgroud)
现在我们了解了如何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)
Run Code Online (Sandbox Code Playgroud)
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]
Run Code Online (Sandbox Code Playgroud)
展开下面的代码片段以验证浏览器中的结果 -
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)Run Code Online (Sandbox Code Playgroud)
并且由于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]
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以说,timeout pick3如果需要的时间超过十 (10) 秒,如果retry超过的话,则整个选秀 -
retry(_ => timeout(pick3(), 10000))
.then(JSON.stringify)
.then(console.log, console.error)
Run Code Online (Sandbox Code Playgroud)
这种以多种方式组合简单函数的能力使得它们比试图独立完成所有事情的大型复杂函数更强大。
当然,这意味着我们可以retry直接应用于您问题中的示例代码 -
async function main () {
await retry(callA, ...)
await retry(callB, ...)
await retry(callC, ...)
return "done"
}
main().then(console.log, console.error)
Run Code Online (Sandbox Code Playgroud)
您可以申请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)
Run Code Online (Sandbox Code Playgroud)
或适用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)
Run Code Online (Sandbox Code Playgroud)
或者也许适用timeout于整个过程 -
async function main () {
await retry(callA, ...)
await retry(callB, ...)
await retry(callC, ...)
return "done"
}
timeout(main(), 30000).then(console.log, console.error)
Run Code Online (Sandbox Code Playgroud)
或者任何其他符合您实际意图的组合!
| 归档时间: |
|
| 查看次数: |
1919 次 |
| 最近记录: |