在以下代码中,所有对
retry
的调用都会收到警告“递归调用不是尾部调用”:
private tailrec suspend fun <T> retry(numberOfRetries: Int, block: suspend () -> T): Result<T> =
runCatching {
Timber.d("retry($numberOfRetries)")
block()
}.fold(
onSuccess = { Result.success(it) },
onFailure = { throwable ->
when (throwable) {
is TimeoutCancellationException -> {
Timber.e(throwable, "Request ran into timeout - retrying immediately...")
if (numberOfRetries > 0) {
retry(numberOfRetries - 1, block)
} else {
Result.failure(throwable)
}
}
is HttpException -> {
if (throwable.code() == HttpStatusCode.NOT_FOUND_404) {
Timber.e(throwable, "Request returned 404 - don't retry!")
Result.failure(throwable)
} else {
Timber.e(throwable, "Request returned some other error code - retrying in 3 seconds...")
if (numberOfRetries > 0) {
delay(DELAY_BEFORE_RETRY_MS)
retry(numberOfRetries - 1, block)
} else {
Result.failure(throwable)
}
}
}
else -> {
Timber.e(throwable, "Some other problem with request - retrying in 3 seconds...")
if (numberOfRetries > 0) {
delay(DELAY_BEFORE_RETRY_MS)
retry(numberOfRetries - 1, block)
} else {
Result.failure(throwable)
}
}
}
}
)
看起来 tailrec 检测没有通过内联高阶函数。您在这里使用
fold
并传递回调,您不是直接从 retry
的主体调用 retry
。
这是一个演示该问题的更简单的示例:
private tailrec fun doThing(count: Int) {
inlineLambdaWrap {
if (count > 0) {
doThing(count - 1) // Warning: Recursive call is not a tail call
} else {
println("STOP")
}
}
}
private inline fun inlineLambdaWrap(block: () -> Unit) {
block()
}
我认为
try
/catch
/finally
块中也不支持尾递归,因此摆脱 runCatching
/fold
在这个意义上没有帮助。
话虽这么说,重试本质上是一种重复,所以我一开始就不会采用递归方法。例如,使用递归使得我们不清楚最终会报告哪个错误。它还在您当前的代码中添加了一堆额外的
if
。这个怎么样?
private suspend fun <T> retryRequest(numberOfRetries: Int, block: suspend () -> T): Result<T> {
lateinit var lastError: Throwable
repeat(numberOfRetries) { attempt ->
try {
Timber.d("retry(${numberOfRetries - attempt})")
return Result.success(block())
} catch (e: TimeoutCancellationException) {
Timber.e(e, "Request ran into timeout - retrying immediately...")
lastError = e
} catch (e: HttpException) {
if (e.code() == HttpStatusCode.NOT_FOUND_404) {
Timber.e(e, "Request returned 404 - don't retry!")
return Result.failure(e)
} else {
Timber.e(e, "Request returned some other error code - retrying in 3 seconds...")
delay(DELAY_BEFORE_RETRY_MS)
lastError = e
}
} catch (t: Throwable) {
Timber.e(t, "Some other problem with request - retrying in 3 seconds...")
delay(DELAY_BEFORE_RETRY_MS)
lastError = t
}
}
return Result.failure(lastError)
}
我什至会说不要在业务代码中使用
Result
,然后扔掉,但这会影响你的签名,所以我不会更改它。