0Pricing
Kotlin Academy · บทเรียน

การส่งต่อ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. SupervisorJob กับ Job: การแยกความล้มเหลว
  2. CoroutineExceptionHandler: ตัวจัดการข้อผิดพลาดที่ไม่ถูกดักจับระดับ Global
  3. การส่งต่อ Exception ของ async/await
  4. การออกแบบสถาปัตยกรรม Coroutine ที่ทนทาน
← กลับไปที่ Kotlin Academy