---
# System prepended metadata

title: try catch encapsulate
tags: [exception handle, Android, kotlin]

---

# try catch encapsulate

#### 以往在執行可能會出現例外錯誤的程式碼，最常使用的是try catch
```kotlin=
       try {
           val response = call()
           if (response.isSuccessful) {
               val body = response.body()
               Timber.d("body = $body")
               if (body != null) return Resource.success(body)
           }
           return error(" ${response.code()} , ${response.message()}")
       } catch (e: Exception) {
           return error(e.message ?: e.toString())
       }
```
#### kotlin的標準函式庫Result幫我們把它封裝了
##### 使用runcaching，他回傳一個Result，底下調用了Result的onSuccess和onFailure方法，你可以在其block再定義分別要執行什麼action
##### 相關連結可以參考[Roman Elizarov的github](https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md)

```kotlin=
lateinit var response: Response<T>
        kotlin.runCatching {
            response = call()
        }.onSuccess {
            if (response.isSuccessful) {
                val body = response.body()
                if (body != null) return Resource.success(body)
            }
        }.onFailure {
            return error(it.message ?: it.toString())
        }
            return error(" ${response.code()} , ${response.message()}")
```

### coroutine Exception handle

#### 在coroutine builder ，如launch || sync，在這兩個block裡面所產生的exception並不會被throw出來，所以你在launch內使用try catch是沒有效果的
* 下面例子 它雖然有catch到exception，但程式還是crash了。
* coroutine的特性是發生exception它並不會throw出來，而是向上廣播直到root cotoutine，再經由CoroutineExceptionHandler處理
```kotlin=
fun main() {
    runBlocking {
        val job1 = GlobalScope.launch() { //root coroutine
            val job2 = launch {  // child coroutine
                throw (Exception("throw error"))
            }
            try {
                job2.join()
            } catch (e: Exception) {
                println("catch exception , ${e.message}")
            }
        }
        job1.join()
    }
}
```
輸出
==catch exception , StandaloneCoroutine is cancelling
Exception in thread "DefaultDispatcher-worker-2" java.lang.Exception: throw error==

* 所以我們在root coroutine 加入CoroutineExceptionHandler處理child coroutine丟出來的exception，程式就不會噴掉了
* 它會等所有child job都被終結才會執行exceptionHandler。
* 加入CoroutineExceptionHandler只能root coroutine (GlobalScope) or top-level coroutine使用(viewModelScope)，目前只知道這兩個...
* 雖然exceptionHandler可以處理所有的exception，但顯得不夠靈活
```kotlin=
fun main() {
    runBlocking {
        val exceptionHandler = CoroutineExceptionHandler { _, Throwable ->
            println("throwable = ${Throwable.message}")
        }
        val job1 = GlobalScope.launch(exceptionHandler) {
            val job2 = launch {
                throw (Exception("throw error"))
            }
            try {
                job2.join()
            } catch (e: Exception) {
                println("catch exception , ${e.message}")
            }
        }
        job1.join()
    }
}
```
輸出
==catch exception , StandaloneCoroutine is cancelling
throwable = throw error==


---

## 取代CoroutineExceptionHandler
### coroutineScope builder
* 它有三個特點
    1. 繼承呼叫者的coroutine context & 支援structured concurrency
    2. child job發生exception時，不會廣播，取而代之的是把exception丟出去(好像是kotlin & Java的設計機制:呼叫一個function，當function內部發生exception，根據這個機制會把exception re-throw，所以呼叫方就會收到exception再作相對應處理)
    3. 當child job其中一個發生exception，會終止其他的job

* 下面例子
    1. fun main()可以像是viewModelScope.launch去呼叫suspend fun
    2. 執行childjob()，在第二個job丟出exception，就終止下面的其他job
    3. 而main就可以catch到這個exception
```kotlin=
fun main() = runBlocking {
    try {
        childjob()
    }catch (e:Exception){
        println("catch exception ${e.message}")
    }
    
    //這個寫法也可以
    kotlin.runCatching {
            childjob()
        }
            .onSuccess { println("job execute success") }
            .onFailure { println("job fail with ${it.message}") }
    }
    //------------------
}

suspend fun childjob(){
    coroutineScope {
        launch {
            println("job 1: ${Thread.currentThread().name}")

        }
        launch {
            throw RuntimeException("throw error job1")
            println("job 2: ${Thread.currentThread().name}")
        }
    }
    println("main job: ${Thread.currentThread().name}")
}
```
輸出
==job 1: main
catch exception throw error job1==

### supervisorScope builder
* 它繼承了coroutineScope builder的前兩個特點
    1. 繼承呼叫者的coroutine context & 支援structured concurrency
    2. child job發生exception時，不會廣播，取而代之的是把exception丟出去
* 額外兩個特點
    3. 當它的child job其中一個發生exception時，不會終結掉其他的job
    4. 在coroutineScope底下所建立的coroutine會成為top-level coroutine(代表你可以調用==CoroutineExceptionHandler==)
    * **但是它並不會把exception丟回給呼叫方，需要在child job帶入CoroutineExceptionHandler處理exception**

```kotlin=
fun main() {
    runBlocking {
        kotlin.runCatching {
            childjobWithsupervisorScope()
        }
            .onSuccess { println("job execute success") }
            .onFailure { println("job fail with ${it.message}") }

    }


}
suspend fun  childjobWithsupervisorScope(){
    supervisorScope {
        val exceptionHandler = CoroutineExceptionHandler { _, Throwable ->
            println("throwable = ${Throwable.message}")
        }
        val job1 = launch {
            println("this is job1")
        }
        val job2 = launch(exceptionHandler) {
            throw RuntimeException("job2 throw error")
            println("this is job2")
        }
        val job3 = launch {
            println("this is job3")
        }
        println("final")
    }
}

```
輸出
==final
this is job1
throwable = job2 throw error
this is job3
job execute success==

## CancellationException

* 在coroutine中使用try catch需要特別注意cancellationException
* 下面的例子，使用delay表示網路請求，等待回傳資料的時間，因為delay是suspend fun，所以在500ms的時候就會執行job cancel了，但是輸出卻看到job繼續在執行
* 因為catch捕捉了所有exception，破壞了structured concurrency，所以job沒有被終止
```kotlin=
fun main() = runBlocking {

    val job = launch {
        try {
            println("Performing network request in Coroutine")
            delay(1000)
        } catch (e: Exception) {
            println("Handled exception in Coroutine")
        }

        println("Coroutine still running ... ")
    }

    delay(500)
    job.cancel()
}
```
輸出
==Performing network request in Coroutine
Handled exception in Coroutine
Coroutine still running ...==

* 多加一個判斷式.如果是CancellationException就把它丟出來，就可以正常取消job了，使用上要多注意
```kotlin=
fun main() = runBlocking {

    val job = launch {
        try {
            println("Performing network request in Coroutine")
            delay(1000)
        } catch (e: Exception) {
            if (e is CancellationException) {
                throw e
            }
            println("Handled exception in Coroutine")
        }

        println("Coroutine still running ... ")
    }

    delay(500)
    job.cancel()
}
```
輸出
==Performing network request in Coroutine==

參考資料
[kotlin官方](https://kotlinlang.org/docs/exception-handling.html#cancellation-and-exceptions)
[blog](https://www.netguru.com/codestories/exceptions-in-kotlin-coroutines)
[Roman github](https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md)
[知乎-coroutine常見錯誤](https://zhuanlan.zhihu.com/p/347784556)
###### tags `exception handle` `kotlin` `Android`