question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Exception handling using launch instead of relying on async

See original GitHub issue

I’m writing a small helper class to bridge some asynchronous functionality via coroutines to some of our Java classes. I’m a little confused about handling errors when using launch coroutines, so I’d like to check my expectations (I’ve read https://github.com/Kotlin/kotlinx.coroutines/issues/61). I’ve gotten this to work successfully using async instead of launch, but I really would like to understand how to do this using launch.

Given the following:

val someData: SomeData? = null

fun returnSomeJob(): Job {
    ...
    //some other operations here
    ...

    return doAsyncThings()
}

fun doAsyncThings(): Job = launch(UI) {
    try {
        someData = getSomeDataAsync()
    } catch (e: Throwable) {
        //do something with the error locally, re-throw to percolate
        throw e
    }
}

suspend fun getSomeDataAsync(): SomeData = getSomeResponse().await().let{ unwrapOrThrow(it) }

fun unwrapOrThrow(someResponse: SomeResponse): SomeData {
    when(someResponse.isSuccessful()) {
        true -> someResponse.getSomeData()
        false -> throw SomeException()
    }
}

What would I need to do to make the following helper class forward errors through a callback properly?

class CoroutineHelper {
    companion object {
        interface CompletionHandler<T> {
            fun onSuccess(value: T)
            fun onError(error: Throwable)
        }

        @Suppress("UNCHECKED_CAST")
        @JvmStatic
        fun <T> handleCompletion(job: Job, handler: CompletionHandler<T>): Job {
            return launch(UI) {
                when (job) {
                //we care about a returned value
                    is Deferred<*> -> {
                        try {
                            "Expecting a return value...".dlog()
                            (job.await() as T).let { handler.onSuccess(it) }
                        } catch (e: Throwable) {
                            handler.onError(e)
                        }
                    }

                // we do not care about a returned value
                    else -> {
                        try {
                            "Not expecting a return value...".dlog()
                            job.join()
                            handler.onSuccess(Unit as T)
                        } catch (e: Throwable) {
                            handler.onError(e)
                        }
                    }
                }
            }
        }
    }
}

I’ve tried adding a CoroutineExceptionHandler to various contexts up the chain, but no combination of launch(UI + CoroutineExceptionHandler { ... }) on any or all coroutine builders achieves what I’m after. launch(UI + CoroutineExceptionHandler{ launch(UI) { ... } }) allows me to intercept errors in the first launch, which I can log, but not re-throw in order to percolate. I’d like to bubble them up to catch them in handleCompletion.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:11 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
jcornazcommented, Apr 25, 2018

CoroutineExceptionHandler also to be avoided?

No sorry. I only meant don’t call callback from launch. This would be against the whole philosophy and it would kill the point of using coroutines. But attaching a CoroutineExceptionHandler is perfectly fine as long as it done is for good reasons (logging for example).

Just don’t do it in order to go back to a callback programming style:

fun notGood(onSuccess: (Int) -> Unit, onError: (Throwable) -> Unit) {
  launch(CoroutineExceptionHandler { _, throwable -> onError(throwable) }) {
    val result = computeAnswerToLifeUniverseAndEverything() // suspend for 7.5 million years and return 42
    onSuccess(result)
  }
}

Logging is a common use case, since if something crashes, you want to be notified even if you don’t need the result…

Logging is indeed a common case of a good usage of CoroutineExceptionHandler. No need to use async if your only concern about failure is logging. And no need to call getCancellationException either.

val ExceptionLogger = CoroutineExceptionHandler { context, throwable ->
  println("$throwable happened in $context") // log
}

fun good() {
  launch(ExceptionLogger) {
    val result = computeAnswerToLifeUniverseAndEverything() // suspend for 7.5 million years and return 42
    println(result)
  }
}

However, in our code base we decided to attach the log directly to the default Thread.UncaughtExceptionHandler, so we don’t have to manually provide a CoroutineExceptionHandler each time we use launch and we think that all uncaught exception should be logged anyway.

0reactions
dave08commented, Apr 25, 2018

even call a callback! (don’t do this).

What do you mean, CoroutineExceptionHandler also to be avoided?

never need to call getCancellationException .

Logging is a common use case, since if something crashes, you want to be notified even if you don’t need the result…

Read more comments on GitHub >

github_iconTop Results From Across the Web

Exception handling: launch vs async - Kotlin Discussions
In the docs, it says;. Coroutine builders come in two flavors: propagating exceptions automatically (launch and actor) or exposing them to users ...
Read more >
Why exception handling with Kotlin Coroutines is so hard and ...
If the async Coroutine fails, the exception is encapsulated in the Deferred return type, and is re-thrown when we call the suspend function...
Read more >
Are You Handling Exceptions in Kotlin Coroutines Properly?
In this article I will try to show situations where you need to be more cautious about exceptions and show some best practices...
Read more >
Kotlin Coroutines by Tutorials, Chapter 8: Exception Handling
Exception and error handling is an integral part of asynchronous programming. Imagine that you initiate an asynchronous operation, it runs through without ...
Read more >
Rx to Coroutines Concepts, Part 2.1: Exceptions
Both async and launch create a Job . The only difference is that you can await on the job you start when you...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found