왜 suspend 함수를 사용해야할까?

shin_stealer·2024년 12월 6일
0

기존 비동기 작업(콜백 기반)의 단점과 코루틴으로 개선된 예시

1. 기존 비동기 작업: 콜백 기반

콜백(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개로 늘어나면 가독성이 더 떨어집니다.

에러 처리 어려움.
에러가 발생했을 때, 어떤 요청에서 발생했는지 파악하기 어렵습니다.
에러 처리를 하려면 각각의 콜백마다 에러를 처리하는 코드를 추가해야 합니다.

2. 코루틴으로 개선된 비동기 작업

코루틴은 비동기 작업을 동기식 코드처럼 작성할 수 있도록 도와줍니다. 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를 사용하여 에러를 처리할 수 있습니다.
어떤 작업에서 에러가 발생했는지 쉽게 파악 가능합니다.

3. 에러 처리 예제

콜백 기반의 에러 처리
에러 처리를 추가하려면 각 콜백마다 별도의 에러 처리 로직을 추가해야 합니다.

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"
}

결론

기존의 콜백 기반 비동기 작업은 중첩 문제와 복잡한 에러 처리 로직으로 인해 코드 가독성이 떨어지는 단점이 있었습니다. 반면, 코루틴은 비동기 작업을 동기식 코드처럼 작성할 수 있게 해주어 가독성, 유지보수성, 디버깅 측면에서 훨씬 유리합니다.
실제 프로젝트에서는 코루틴을 사용해 효율적으로 비동기 작업을 관리하는 것이 권장됩니다. 😊

profile
I am a Blacksmith.

0개의 댓글