我希望一些JavaScript代码将3件事作为参数:
- 返回承诺的函数。
- 最大尝试次数。
- 每次尝试之间的延迟。
我最终做的是使用for
循环。我不想使用递归函数:这样,即使有 50 次尝试,调用堆栈也不会长 50 行。
以下是代码的打字稿版本:
/**
* @async
* @function tryNTimes<T> Tries to resolve a {@link Promise<T>} N times, with a delay between each attempt.
* @param {Object} options Options for the attempts.
* @param {() => Promise<T>} options.toTry The {@link Promise<T>} to try to resolve.
* @param {number} [options.times=5] The maximum number of attempts (must be greater than 0).
* @param {number} [options.interval=1] The interval of time between each attempt in seconds.
* @returns {Promise<T>} The resolution of the {@link Promise<T>}.
*/
export async function tryNTimes<T>(
{
toTry,
times = 5,
interval = 1,
}:
{
toTry: () => Promise<T>,
times?: number,
interval?: number,
}
): Promise<T> {
if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
let attemptCount: number;
for (attemptCount = 1; attemptCount <= times; attemptCount++) {
let error: boolean = false;
const result = await toTry().catch((reason) => {
error = true;
return reason;
});
if (error) {
if (attemptCount < times) await delay(interval);
else return Promise.reject(result);
}
else return result;
}
}
上面使用的delay
函数是一个承诺超时:
/**
* @function delay Delays the execution of an action.
* @param {number} time The time to wait in seconds.
* @returns {Promise<void>}
*/
export function delay(time: number): Promise<void> {
return new Promise<void>((resolve) => setTimeout(resolve, time * 1000));
}
澄清一下:上面的代码有效,我只是想知道这是否是一种"好"的方法,如果不是,我该如何改进它。
有什么建议吗?提前感谢您的帮助。
我不想使用递归函数:这样,即使有 50 次尝试,调用堆栈也不会长 50 行。
这不是一个好的借口。调用堆栈不会从异步调用中溢出,当递归解决方案比迭代解决方案更直观时,您可能应该选择它。
我最终做的是使用
for
循环。这是一种"好"的方法吗,如果不是,我该如何改进它?
for
循环很好。有点奇怪,它从 1
开始,不过,基于 0 的循环更惯用。
然而,不好的是你奇怪的错误处理。该布尔error
标志不应在代码中占有一席之地。使用.catch()
很好,但try
/catch
也可以工作,应该首选。
export async function tryNTimes<T>({ toTry, times = 5, interval = 1}) {
if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
let attemptCount = 0
while (true) {
try {
const result = await toTry();
return result;
} catch(error) {
if (++attemptCount >= times) throw error;
}
await delay(interval)
}
}
你可能想看看异步重试,它完全符合你的需要。此软件包允许您重试异步操作,并且您可以配置(除其他事项外(重试之间的超时(即使因素增加(、最大重试次数......
这样,您就不必重新发明轮子,而是可以依靠在社区中广泛使用的经过验证的软件包。
将递归函数与 Promise 一起使用不会成为调用堆栈的问题,因为 Promise 会立即返回,并且 then
或 catch
函数将在异步事件后调用。
一个简单的javascript函数是这样的:
function wait (ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
function retry (fn, maxAttempts = 1, delay = 0, attempts = 0) {
return Promise.resolve()
.then(fn)
.catch(err => {
if (attempts < maxAttempts) {
return retry (fn, maxAttempts, delay, attempts + 1)
}
throw err
})
}
你考虑过RxJS吗?
它非常适合在异步工作流中实现这种逻辑。
下面是一个示例,说明如何在不破坏公共 API 的情况下执行此操作(即从 Promise 转换为 Observable 并返回(。在实践中,您可能希望在任何给定项目中使用 RxJS 或 Promises,而不是混合它们。
/**
* @async
* @function tryNTimes<T> Tries to resolve a {@link Promise<T>} N times, with a delay between each attempt.
* @param {Object} options Options for the attempts.
* @param {() => Promise<T>} options.toTry The {@link Promise<T>} to try to resolve.
* @param {number} [options.times=5] The maximum number of attempts (must be greater than 0).
* @param {number} [options.interval=1] The interval of time between each attempt in seconds.
* @returns {Promise<T>} The resolution of the {@link Promise<T>}.
*/
export async function tryNTimes<T>(
{
toTry,
times = 5,
interval = 1,
}:
{
toTry: () => Promise<T>,
times?: number,
interval?: number,
}
): Promise<T> {
if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
let attemptCount: number;
return from(toTry)
.pipe(
retryWhen(errors =>
errors.pipe(
delay(interval * 1000),
take(times - 1)
)
)
)
.toPromise();
}
可能不值得为这一个逻辑添加整个库,但是如果您的项目涉及许多复杂的异步工作流,那么RxJS很棒。
这里有一些有效的代码。
助手
interface RetryProps {
attempts?: number
delay: number
fn: () => boolean
maxAttempts: number
}
function retry({ fn, maxAttempts = 1, delay = 1000, attempts = 5 }: RetryProps) {
return new Promise((resolve, reject) => {
if (fn()) resolve(true)
else {
if (attempts < maxAttempts) {
setTimeout(
() =>
retry({ fn, maxAttempts, delay, attempts: attempts + 1 })
.then(() => resolve(true))
.catch((err) => reject(err)),
delay
)
} else reject('Could not resolve function.')
}
})
}
然后向它传递一个函数,该函数在成功时返回true
。
使用示例
retry({
fn: function () {
// Whatever you want to test, return true upon success.
const elExists = !document.getElementById('myRandomELement')
console.log('Element present in DOM?', elExists)
return elExists
},
maxAttempts: 4,
delay: 2000,
})
.then(() => console.log('Done'))
.catch(() => console.log("Didn't pan out"))
由于它返回了一个承诺,因此您可以await
它或使用then/catch
来解决它。
检查这个纯javascript异步重试库。
例
const { retry } = require('@ajimae/retry')
function exec() {
// This will be any async or sync action that needs to be retried.
return new Promise(resolve => {
setTimeout(() => {
resolve({ message: 'some async data' })
}, 1500)
})
}
// takes the response from the exec function and check if the condition/conditions are met
function predicate(response, retryCount) => {
console.log(retryCount) // goes from 0 to maxRetries
// once this condition is met the retry exits
return (response == 200)
}
(async function main() {
// enable or disable an exponential backoff behaviour if needed.
const result = await retry(exec, predicate, { maxRetries: 5, backoff: true })
console.log(result) // { message: 'some async data' }
})()
PS:我创作了这个库。