การส่งต่อ Exception ของ async/await
ทำความเข้าใจว่า exception จาก async ถูกส่งต่ออย่างไร และเมื่อใดควรใช้ try-await
การส่งต่อ Exception ของ async/await เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน 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 + รอผลเสมอ
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}")
}async ภายใต้ coroutineScope
ภายใต้ 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}")
}
}async ภายใต้ supervisorScope
ภายใต้ 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}") }
}
}awaitAll สำหรับ async หลายรายการ
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}")
}
}runCatching กับ async
ห่อ 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
ภายใต้การทำงานพร้อมกันแบบมีโครงสร้าง deferred ที่สร้างด้วย async จะเชื่อมโยงกับขอบเขตแม่เสมอ เมื่อขอบเขตถูกยกเลิก 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 เสมอ
เรียก await() กับ deferred ที่สร้างด้วย async ทุกรายการเสมอ แม้จะไม่ต้องการผลลัพธ์ก็ตาม วิธีนี้ทำให้เห็นข้อยกเว้นและป้องกันความล้มเหลวที่เกิดขึ้นอย่างเงียบ ๆ
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 การรอผลแต่ละครั้งต้องจัดการความล้มเหลวของตนเอง รอผล deferred ทุกรายการเสมอ
คำถามที่พบบ่อย
บทเรียน “การส่งต่อ Exception ของ async/await” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การส่งต่อ Exception ของ async/await” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การส่งต่อ Exception ของ async/await”
ทำความเข้าใจว่า exception จาก async ถูกส่งต่ออย่างไร และเมื่อใดควรใช้ try-await คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การส่งต่อ Exception ของ async/await” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม
ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- SupervisorJob กับ Job: การแยกความล้มเหลว
- CoroutineExceptionHandler: ตัวจัดการข้อผิดพลาดที่ไม่ถูกดักจับระดับ Global
- การส่งต่อ Exception ของ async/await
- การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน