https://kotlinlang.org logo
n

Nino

10/07/2022, 10:21 AM
Hello, I'm wondering if it's OK to do this kind of code. If I understood correctly, Coroutines use exceptions internaly to "communicate" between child and parent about their state (and propagate cancellation via
CancellationException
). Wouldn't using a big
catch (exception: Exception)
around a suspending function
api.getFooResponse()
defeat this purpose and prevent any coroutine calling
getFoo()
to be cancelled correctly ? Wouldn't the
CancellationException
be "swallowed" ? Should we re-throw it ?
Copy code
suspend fun getFoo(): Foo? = try {
        val fooResponse = api.getFooResponse()
        Foo(bar = fooResponse.bar)
    } catch (exception: Exception) {
        exception.printStackTrace()
        null
    }
👍 1
s

Sam

10/07/2022, 10:25 AM
Really the important thing is that when you receive a
CancellationException
you should stop what you’re doing. You don’t necessarily need to rethrow the exception. The coroutine will still remember that it has been cancelled, and will prevent you from suspending again. But rethrowing it is a good way to make sure that everything exits quickly and doesn’t start trying to do more work.
e

ephemient

10/07/2022, 10:41 AM
if you don't rethrow, the next coroutine operation will throw a new exception, so there's not a lot you can do in that state
(which is also the case in a
finally
block, which runs even if the exception was
CancellationException
, whether it was caught or not)
s

Sam

10/07/2022, 10:57 AM
I guess the worst thing would be if
getFoo
turns out to be in a loop, e.g.
Copy code
while (foo == null) foo = getFoo()
😬 in that case definitely rethrow the exception 😄
n

Nino

10/07/2022, 1:33 PM
I can't find any extension or utility method for coroutines (there is
Flow.catch {}
tho), so I guess I would have to write my own ? 1
Copy code
fun Throwable.rethrowCancellationOrNull(): Nothing? = if (this is CancellationException) {
    throw this
} else {
    null
}
Example :
Copy code
suspend fun getFoo(): Foo? = try {
        val fooResponse = api.getFooResponse()
        Foo(bar = fooResponse.bar)
    } catch (exception: Exception) {
        exception.printStackTrace()
        exception.rethrowCancellationOrNull()
    }
2
Copy code
suspend fun <T, R> T.runCatchingSuspendOrNull(block: suspend T.() -> R): R? {
    return try {
        block()
    } catch (e: Throwable) {
        e.printStackTrace()
        if (e is CancellationException) {
            throw e
        }
        null
    }
}
Example :
Copy code
suspend fun getFoo(): Foo? = runCatchingSuspendOrNull {
    val fooResponse = api.getFooResponse()
    Foo(bar = fooResponse.bar)
}
What do you think ?
s

Sam

10/07/2022, 1:47 PM
In an ideal world, you wouldn’t need to catch something as broad as
Exception
or
Throwable
at all. But if you really have to do that, both of those options seem like good chocies
#arrow also has NonFatal (and nonFatalOrThrow) if you’re looking for something pre-made
n

Nino

10/07/2022, 1:51 PM
Thanks, I'll check that ! Yes the better approach would be to catch only the type of Exception that
getFooResponse()
could throw... but I have no idea what exception could be thrown, and maybe tomorrow a library update would silently add a new thrown exception type, and neither I nor the compiler would know about it. I really hate Exceptions in Kotlin, I really wish everything would be sealed classes instead. ^^
l

Lukasz Kalnik

10/10/2022, 11:10 AM
#arrow Either type is quite useful for domain error representation.
4 Views