Kotlin coroutines - gracefully handling errors from suspend functions

Viewed 4406

Trying to implement graceful handling of the errors with suspend functions that are called from async methods, How to catch the error thrown by a suspend method.

suspend fun findById(id: Long): User? {
    throw Exception("my exception") // intentionally throwing to simulate error situation.
    return userModel.findById(id) // IO, may throw an error
}

Caller piece, launching with IO thread

GlobalScope.launch(Dispatchers.IO) {
    try {

        var userAsync: Deferred<User?>? = null
        arguments?.getLong("id")?.let {
            userAsync = async { viewModel?.findById(it) } // async for efficiency as i've other async methods too.
        }

        val data = userAsync?.await()

        withContext(Dispatchers.Main) {
            user = data // data binding, populating UI fields of user
        }
    } catch (exception: Exception) {
        withContext(Dispatchers.Main) { fault(exception) }
    }
}

fault method

private fun fault(exception: Exception) {
    Log.d("User", "fault: ${exception.localizedMessage}") // expecting output
}

Currently runtime is crashing, want to implement graceful handling of errors.


Attempt 2

Tried placing try catch within the async block but it didn't like it.

var userAsync: Deferred<UserVO?>? = null
arguments?.getLong("id")?.let {
    userAsync = async {
        try {
            delegate?.findById(it)
        } catch (e: Exception) {
            print(e)
        }
    }
}
1 Answers

I would use a CoroutineExceptionHandler to make your coroutines fail gracefully:

1) Define the handler:

val exceptionHandler = CoroutineExceptionHandler { context, error ->
    // Do what you want with the error
    Log.d(TAG, error)
}

2) Refactor your findById function to be executed within an IO context and make your ui code main safe:

suspend fun findById(id : Int) : User? = withContext(Dispatchers.IO){
    when(id){
        0 -> throw Exception("not valid")
        else -> return@withContext User(id)
    }
}
  1. Launch your job within MainScope (and so update the ui), passing exceptionHandler to launch coroutine builder in order to catch the exception:
val exceptionHandler = CoroutineExceptionHandler { _, error ->
     // Do what you want with the error
    Log.d(TAG, error)
}

MainScope().launch(exceptionHandler) {
     val user = delegate?.findById(userId)
        user?.let {
            Timber.v(it.toString())
        }
}
Related