async/await 예외 전파
async에서 발생한 예외가 어떻게 전파되는지, 그리고 try-await를 언제 사용하는지 이해해 보세요.
async/await 예외 전파은(는) CoddyKit의 무료 Kotlin Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Kotlin Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Kotlin Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
async는 예외를 저장합니다
launch와 달리 async는 반환된 Deferred에 예외를 저장합니다. await()를 호출할 때만 예외가 다시 발생합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred = async {
throw RuntimeException("async error")
}
try {
deferred.await() // exception rethrown here
} catch (e: RuntimeException) {
println("Caught: ${e.message}")
}
}await() 없이 발생한 예외
await()를 전혀 호출하지 않으면 일반 Job 부모에서는 예외가 조용히 버려집니다. supervisorScope와 항상 await하는 방식을 권장합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
// Exception stored in deferred, never retrieved:
val d = async { throw RuntimeException("lost exception") }
delay(100) // d has failed — exception never surfaced
println("d.isCancelled: ${d.isCancelled}")
}coroutineScope에서의 async
일반 coroutineScope에서 async 자식 작업이 예외를 발생시키고 그 예외가 전파되면, 즉 예외를 잡지 않으면 범위와 모든 형제 작업이 취소됩니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
try {
coroutineScope {
val a = async { "A" }
val b = async { throw RuntimeException("B failed") }
println(a.await())
println(b.await()) // propagates, cancels scope
}
} catch (e: RuntimeException) {
println("Scope failed: ${e.message}")
}
}supervisorScope에서의 async
supervisorScope에서는 async에서 발생한 예외가 형제 작업으로 전파되지 않습니다. 각 await()`는 개별적으로 감싸야 합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
supervisorScope {
val a = async { "A" }
val b = async { throw RuntimeException("B failed") }
println(a.await())
try { println(b.await()) }
catch (e: RuntimeException) { println("b failed: ${e.message}") }
}
}여러 async 작업에 awaitAll 사용
awaitAll(d1, d2, d3)은 모든 deferred를 기다립니다. 하나라도 실패하면 즉시 예외를 발생시키고 나머지를 취소합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
try {
val results = awaitAll(
async { "result1" },
async { throw RuntimeException("task 2 failed") },
async { "result3" }
)
println(results)
} catch (e: Exception) {
println("awaitAll failed: ${e.message}")
}
}async와 runCatching
빠른 실패로 중단하지 않고 결과와 오류를 수집하려면 각 await()를 runCatching으로 감쌉니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
supervisorScope {
val deferreds = listOf(
async { "A" },
async { throw RuntimeException("B") },
async { "C" }
)
val results = deferreds.map { runCatching { it.await() } }
results.forEach { println(it) }
}
}오류를 누적하는 병렬 작업
병렬 async 작업에서 발생한 모든 실패를 수집하여 빠르게 실패시키는 대신 함께 보고합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val tasks = listOf("a", "b", "c")
val results = supervisorScope {
tasks.map { t ->
async {
if (t == "b") throw RuntimeException("b failed")
t.uppercase()
}
}.map { runCatching { it.await() } }
}
val errors = results.filter { it.isFailure }
val successes = results.mapNotNull { it.getOrNull() }
println("Success: $successes, Errors: ${errors.size}")
}예외 유형 보존
async 내부에서 발생한 예외는 유형이 보존됩니다. 정확하게 오류를 처리하려면 await()에서 구체적인 예외 유형을 포착합니다.
import kotlinx.coroutines.*
class NetworkException(msg: String) : RuntimeException(msg)
fun main() = runBlocking {
val d = async { throw NetworkException("timeout") }
try {
d.await()
} catch (e: NetworkException) {
println("Network error: ${e.message}")
} catch (e: Exception) {
println("Other error: ${e.message}")
}
}일시 중단 없는 확인을 위한 Deferred.getCompleted()
join() 후에는 getCompleted()를 사용하여 결과를 동기적으로 가져옵니다. deferred가 실패했다면 예외가 발생합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val d = async { 42 }
d.join() // wait without caring about result
try {
val result = d.getCompleted() // synchronous — no suspend
println("Result: $result")
} catch (e: Exception) {
println("Failed: ${e.message}")
}
}구조화된 동시성과 async
구조화된 동시성에서는 async deferred가 항상 부모 범위에 연결됩니다. 범위가 취소되면 deferred도 취소되므로 고아 작업이 남지 않습니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob())
val d = scope.async {
delay(1000)
"result"
}
scope.cancel() // cancels d too
try { d.await() }
catch (e: CancellationException) { println("Cancelled as expected") }
}모범 사례: 항상 Await 사용
결과가 필요하지 않더라도 모든 async deferred에 항상 await()를 호출합니다. 그러면 예외가 드러나고 조용한 실패를 방지할 수 있습니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
supervisorScope {
val d = async {
// do background work
"done"
}
// Always await:
val result = runCatching { d.await() }
println(result)
}
}빠른 확인
async { throw ... } 블록에서 발생한 예외는 언제 드러납니까?
복습
async는 예외를 Deferred에 저장하고, 예외는 await()에서 드러납니다. coroutineScope에서는 전파로 인해 형제 작업이 취소됩니다. supervisorScope에서는 각 await가 자체 실패를 처리해야 합니다. 모든 deferred에 항상 await를 호출합니다.
자주 묻는 질문
“async/await 예외 전파” 강의는 무료인가요?
네 — “async/await 예외 전파” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Kotlin Academy 강의 전체를 잠금 해제할 수 있습니다. Kotlin Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“async/await 예외 전파”에서 뭘 배우나요?
async에서 발생한 예외가 어떻게 전파되는지, 그리고 try-await를 언제 사용하는지 이해해 보세요. 브라우저에서 직접 실행하는 실습 코드로 Kotlin Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Kotlin Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Kotlin Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“async/await 예외 전파” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Kotlin Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Kotlin Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- SupervisorJob과 Job 비교: 실패 격리
- CoroutineExceptionHandler: 전역 미처리 예외 처리기
- async/await 예외 전파
- 복원력 있는 코루틴 아키텍처 설계