아래의 함수는 OneShot 으로 어떤 데이터를 가져올 때 많이 구현하게 되는 코드모양새 입니다.
그리고 aUseCase 에서 어떤 에러가 발생하면 runCatching 에 Catch 되어 최종적으로 onFailure 메서드가 호출 됩니다.
fun fetchData() {
viewModelScope.launch {
runCatching {
withContext(Dispatchers.XX) { aUseCase() }
}.onSuccess { aResult ->
}.onFailure {
}
}
}
하지만 이따금식 여러개의 데이터를 가져와야하는 경우가 있습니다.
fun fetchData() {
viewModelScope.launch {
runCatching {
listOf(async { aUseCase() }, async { bUseCase() }).awaitAll()
}.onSuccess { (aResult, bResult) ->
}.onFailure {
}
}
}
위와 같은 함수가 있다고 가정해봅시다.
fetchData 는 aUseCase, bUseCase 를 병렬로 실행해서 두 값이 모두 도착할 때 까지 기다리는 함수를 가지고 있습니다.
그리고 runCatching 으로 Wrapping 되어 있어서 에러가 생기면 onFailure 가 호출되면서 문제 없을 것 같이 생겼습니다.
하지만 실제로 UseCase 내부에서 네트워크 에러나 특정에러 상황을 만들어서 실행하면 viewModelScope.launch 부분에서 크래시가 발생합니다. 크래시가 발생하는 이유는 간단 합니다. 코루틴에서 Job 에서의 에러는 상위로 전파됩니다. 그리고 async 함수의 반환 값을 보면 Deffred<T> 입니다 Job 이 아니라고 생각할 수 있지만, Job 을 상속하고 있는 클래스중 하나가 Deffred 클래스 입니다.
viewModel.launch 에게 전달 되는 이유도 간단합니다. 코루틴 빌더 함수의 launch 함수가 Job 을 반환하기 때문 입니다.
따라서 하위(async)의 job 의 error 가 상위(launch)의 job 에 전파되게 됩니다.
또한 이것으로 맨처음의 WithContext 를 사용한 함수도 asycn().await() 형식을 취하면 viewModel.launch 에서 충분히 에러가 발생한다는 것도 알게 되었습니다.
이것을 해결하는데는 2가지의 방법이 있습니다.
첫번째는 supervisorScope 를 사용하는 방식입니다.
fun fetchData() {
viewModelScope.launch {
supervisorScope {
runCatching {
listOf(async { aUseCase() }, async { bUseCase() }).awaitAll()
}.onSuccess { (aResult, bResult) ->
}.onFailure {
}
}
}
}
와 같이 처리하게 되면, a,b UseCase 에서 에러가 발생하면 상위의 job 인 launch 에게 전파되지 않고 runCatching 에 catch 되어 onFailure 가 호출 됩니다.
두번째는 SupervisorJob 을 async 함수의 파라미터로 넘겨주는 방법 입니다. 위와 같은 방식으로 launch 에 전파되지 않고 runCatching 에서 catch 되고 onFailure 가 최종적으로 호출 됩니다.
fun fetchData() {
viewModelScope.launch {
runCatching {
listOf(async(SupervisorJob()) { aUseCase() }, async()SupervisorJob() { bUseCase() }).awaitAll()
}.onSuccess { (aResult, bResult) ->
}.onFailure {
}
}
}
그럼 20000