[Kotlin] Flow Retry 연산자
Kotlin Flow에서 연산자를 사용하여 작업을 재시도하는 방법에 대해서는 두 연산자가 있다.
- retryWhen
- retry
retryWhen
retryWhen의 정의
fun <T> Flow<T>.retryWhen(predicate: suspend FlowCollector<T>.(cause: Throwable, attempt: Long) -> Boolean): Flow<T>
그리고 다음과 같이 사용한다.
.retryWhen { cause, attempt ->
}
여기에는 다음과 같은 두 가지 매개변수가 있다.
- cause : cause는 예외의 원인을 제공하는 변수이다. 만약에 함수가 진행하면서 예외가 발생한다면 어떤한 예외인지를 나타낸다.
- attempt : 현재 시도를 나타내는 변수이다. 0부터 시작한다.
예를 들어, 작업을 시작할 때 예외가 있다면 원인(예외)과 시도(0)를 받게 된다.
retryWhen은 predicate 함수를 사용하여 재시도 여부를 결정한다.
술어 함수가 참으로 돌아오면, 재시도를 하고 그렇지 않으면 재시도하지 않는다.
예시 코드
.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(2000)
return@retryWhen true
} else {
return@retryWhen false
}
}
이 경우 원인이 IO 예외이고 시도 횟수가 3회 미만일 때 참으로 돌아오는 것이다.
따라서 조건이 충족된 경우에만 재시도되는 것이다.
참고: predicate 함수가 정지 함수이므로, 그로부터 다른 정지 함수를 호출할 수 있다.
상기 코드에서 알아차린 경우, 우리는 지연(2000)을 호출하여 2초의 지연 후에만 재시도한다.
retry
retry함수의 정의
fun <T> Flow<T>.retry(
retries: Long = Long.MAX_VALUE,
predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T>
아래는 Kotlin Flow 소스 코드의 전체 블록이다.
fun <T> Flow<T>.retry(
retries: Long = Long.MAX_VALUE,
predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T> {
require(retries > 0) { "Expected positive amount of retries, but had $retries" }
return retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
}
retry함수를 실제로 보면 retryWhen을 내부적으로 호출하는 것을 볼 수있다.
retry 함수에는 기본 인수가 있다.
- retries를 전달하지 않으면 Long.MAX_VALUE를 사용한다.
- predicate를 전달하지 않으면 true가 된다.
예를 들어, 아래와 같은 경우
.retry()
작업이 성공적으로 완료할때까지 게속 재시도 한다.
.retry(3)
3번만 재시도합니다.
.retry(retries = 3) { cause ->
if (cause is IOException) {
delay(2000)
return@retry true
} else {
return@retry false
}
}
여기서는 위의 retryWhen을 사용하여 수행한 것과 매우 유사해집니다.
여기서는 원인이 IOException일 때 참으로 돌아오는 것이다. 따라서 원인이 IOException일 때만 재시도됩니다.
지연(2000)을 호출하여 2초의 지연 후에만 재시도한다.
아래는 장기 실행 작업을 시뮬레이션 하는 코드이다.
private fun doLongRunningTask(): Flow<Int> {
return flow {
// your code for doing a long running task
// Added delay, random number, and exception to simulate
delay(2000)
val randomNumber = (0..2).random()
if (randomNumber == 0) {
throw IOException()
} else if (randomNumber == 1) {
throw IndexOutOfBoundsException()
}
delay(2000)
emit(0)
}
}
retry연산자를 사용한다면
viewModelScope.launch {
doLongRunningTask()
.flowOn(Dispatchers.Default)
.retry(retries = 3) { cause ->
if (cause is IOException) {
delay(2000)
return@retry true
} else {
return@retry false
}
}
.catch {
// error
}
.collect {
// success
}
}
마찬가지로 retryWhen연산자를 사용한다면
viewModelScope.launch {
doLongRunningTask()
.flowOn(Dispatchers.Default)
.retryWhen { cause, attempt ->
if (cause is IOException && attempt < 3) {
delay(2000)
return@retryWhen true
} else {
return@retryWhen false
}
}
.catch {
// error
}
.collect {
// success
}
}
우리가 볼 때마다 2초 단위의 지연을 추가하지만 실제 사용 사례에서는 지수 백오프를 사용하여 지연을 추가한다.
지수 백오프 지연을 사용하는 retry연산자
지수 백오프를 사용하여 지연에 대한 코드를 추가한 후
viewModelScope.launch {
var currentDelay = 1000L
val delayFactor = 2
doLongRunningTask()
.flowOn(Dispatchers.Default)
.retry(retries = 3) { cause ->
if (cause is IOException) {
delay(currentDelay)
currentDelay = (currentDelay * delayFactor)
return@retry true
} else {
return@retry false
}
}
.catch {
// error
}
.collect {
// success
}
}
여기서는 두가지 변수를 만들었다.
- currentDelay : 이는 현재 재시도에 사용될 지연을 나타낸다.
- DelayFactor : DelayFactor를 사용하여 currentDelay와 곱하여 다음 재시도에 대한 지연 시간을 늘립니다.
참고
https://amitshekhar.me/blog/retry-operator-in-kotlin-flow