기존 비동기 작업(콜백 기반)의 단점과 코루틴으로 개선된 예시
콜백(callback) 방식의 구조
콜백 방식은 비동기 작업이 끝난 후 실행될 코드를 함수로 전달하는 방식입니다. 하지만 작업이 많아질수록 콜백 지옥(Callback Hell)이라고 불리는 문제가 발생합니다.
코드가 중첩되면서 읽기 어렵고, 디버깅도 힘들어집니다.
예제: 네트워크 요청 후 데이터 처리
아래는 콜백 기반으로 두 개의 네트워크 요청을 순차적으로 실행하는 예입니다.
kotlin
fun fetchData(callback: (String) -> Unit) {
// 네트워크 요청 A
simulateNetworkRequest("Request A") { resultA ->
// 네트워크 요청 B (A의 결과를 이용)
simulateNetworkRequest("Request B based on $resultA") { resultB ->
// 최종 데이터 처리
callback(resultB)
}
}
}
fun simulateNetworkRequest(data: String, callback: (String) -> Unit) {
Thread {
Thread.sleep(2000) // 네트워크 요청 시뮬레이션 (2초 대기)
callback("$data - Response")
}.start()
}
fun main() {
fetchData { finalResult ->
println("최종 결과: $finalResult")
}
}
문제점
콜백 안에 콜백을 정의하다 보면 코드가 오른쪽으로 점점 밀리면서 복잡해집니다.
요청이 3개, 4개로 늘어나면 가독성이 더 떨어집니다.
에러 처리 어려움.
에러가 발생했을 때, 어떤 요청에서 발생했는지 파악하기 어렵습니다.
에러 처리를 하려면 각각의 콜백마다 에러를 처리하는 코드를 추가해야 합니다.
코루틴은 비동기 작업을 동기식 코드처럼 작성할 수 있도록 도와줍니다. suspend 함수를 사용하면 중첩된 콜백 없이 작업의 흐름을 순차적으로 작성할 수 있습니다.
코드 예제: 동일한 작업을 코루틴으로 구현
kotlin
import kotlinx.coroutines.*
suspend fun fetchData(): String {
val resultA = simulateNetworkRequest("Request A")
val resultB = simulateNetworkRequest("Request B based on $resultA")
return resultB
}
suspend fun simulateNetworkRequest(data: String): String {
delay(2000) // 네트워크 요청 시뮬레이션 (2초 대기)
return "$data - Response"
}
fun main() = runBlocking {
val finalResult = fetchData()
println("최종 결과: $finalResult")
}
개선된 점
코드 가독성:
작업 순서를 동기식 코드처럼 직관적으로 작성할 수 있습니다.
네트워크 요청 A → B → 결과 처리의 흐름이 명확합니다.
에러 처리 간편화:
try-catch를 사용하여 에러를 처리할 수 있습니다.
어떤 작업에서 에러가 발생했는지 쉽게 파악 가능합니다.
콜백 기반의 에러 처리
에러 처리를 추가하려면 각 콜백마다 별도의 에러 처리 로직을 추가해야 합니다.
kotlin
fun fetchData(callback: (String?, Throwable?) -> Unit) {
simulateNetworkRequest("Request A") { resultA, errorA ->
if (errorA != null) {
callback(null, errorA)
return@simulateNetworkRequest
}
simulateNetworkRequest("Request B based on $resultA") { resultB, errorB ->
if (errorB != null) {
callback(null, errorB)
return@simulateNetworkRequest
}
callback(resultB, null)
}
}
}
fun simulateNetworkRequest(
data: String,
callback: (String?, Throwable?) -> Unit
) {
Thread {
try {
Thread.sleep(2000)
callback("$data - Response", null)
} catch (e: Exception) {
callback(null, e)
}
}.start()
}
코루틴 기반의 에러 처리
코루틴에서는 try-catch로 간단하게 처리할 수 있습니다.
kotlin
suspend fun fetchData(): String {
return try {
val resultA = simulateNetworkRequest("Request A")
val resultB = simulateNetworkRequest("Request B based on $resultA")
resultB
} catch (e: Exception) {
"에러 발생: ${e.message}"
}
}
suspend fun simulateNetworkRequest(data: String): String {
delay(2000)
if (data.contains("Error")) throw Exception("네트워크 오류!")
return "$data - Response"
}
기존의 콜백 기반 비동기 작업은 중첩 문제와 복잡한 에러 처리 로직으로 인해 코드 가독성이 떨어지는 단점이 있었습니다. 반면, 코루틴은 비동기 작업을 동기식 코드처럼 작성할 수 있게 해주어 가독성, 유지보수성, 디버깅 측면에서 훨씬 유리합니다.
실제 프로젝트에서는 코루틴을 사용해 효율적으로 비동기 작업을 관리하는 것이 권장됩니다. 😊