-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Exception handling using launch instead of relying on async #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
First, as a general advice, always prefer suspending functions over functions returning Then for a usage from Java I would personally convert any So the object CoroutineHelper {
@JvmStatic
fun <T> asFuture(defferred: Deferred<T>): CompletableFuture<T> {
val result = CompletableFuture<T>()
defferred.invokeOnCompletion { exception ->
if (exception != null) {
result.completeExceptionally(exception)
} else {
result.complete(defferred.getCompleted())
}
}
return result
}
@JvmStatic
fun asFuture(job: Job): CompletableFuture<Void> {
val result = CompletableFuture<Void>()
job.invokeOnCompletion { exception ->
if (exception != null) {
result.completeExceptionally(exception)
} else {
result.complete(null)
}
}
return result
}
} Then you can use the Note that kotlinx-coroutines-jdk8 give you |
@jcornaz * **Note**: This function is a part of internal machinery that supports parent-child hierarchies
* and allows for implementation of suspending functions that wait on the Job's state.
* This function should not be used in general application code.
* Implementations of `CompletionHandler` must be fast and _lock-free_.
*/
public fun invokeOnCompletion(onCancelling: Boolean = false, handler: CompletionHandler): DisposableHandle In general, I'm aware that there are probably better ways to handle coroutine interop between Kotlin and Java, but some of the required tools are unavailable to me in this specific instance. Mostly, I'd like to know how to do the things mentioned in the OP for my own edification. |
@carterhudson, suspend function may be used from Java. You have to pass one more argument at the end of type Your reasons to not use fun Job.onCompletion(callback: (Throwable?) -> Unit) = launch(Unconfined) {
join()
var exception: Throwable? = getCancellationException()
if (!isCancelled && exception is JobCancellationException) {
exception = exception.cause // will be null if the job completed normally
}
callback(exception)
} Using Here is another example using RxJava and kotlinx-coroutines-rx2: object CoroutineHelper {
@JvmStatic
fun <T> asSingle(deferred: Deferred<T>): Single<T> = deferred.asSingle(Unconfined)
@JvmStatic
fun asCompletable(job: Job): Completable = job.asCompletable(Unconfied)
} Of course you could even write your own operators on Coroutines' benefits relies on the language keyword But of course from Kotlin using |
I wasn't aware of kotlinx-coroutines-rx2; I'm actually a big fan of reactive extensions. I might pull that in and go down that path.
As for converting as much Java to Kotlin as possible... if only they'd let me 😁 . Thanks for the info! I am still rather curious what I'm doing wrong with |
It looks like you expect This is why Finally if, despite it is contrary to the philosophy, you really want to handle the exception of a Here's an example: suspend fun Job.joinOrThrow() {
join()
var exception: Throwable? = getCancellationExcepion()
if (!isCancelled && exception is JobCancellationException) {
exception = exception.cause // will be null if the job completed normally
}
if (exception != null) throw exception
} Note that |
@jcornaz, I was also looking for such a thing, I'm not a fan of having
just for seeing if there was an exception... Wouldn't that be a great addition to the lib? Also don't you mean '!is JobCancellationException'? |
No i meant
IMO no. In fact I would even advice to not use the This is actually the difference between Also keep in mind that Finally, let's assume we want to add it in the library. How should it behave? If we continue to delegate the exception to the However I agree it is not intuitive. I felt myself in the trap the first time I used it. May be it should be better documented? |
Thanks for the explanation! It is currently very confusing... From what I understand, the best way to handle launch exceptions is either using try catch in launch itself or using a CoroutineExceptionHandler which is like an onError handler in rx and the gain is to not require joining... It's not really fire and forget, but that the calling code can forget it... I always thought this is a subject missing documentation, but mainly in terms of best practices and clear use cases... Also, funny that after isCancelled is false, the exception should be JobCancellationException? The naming is a bit misleading... |
Indeed that what is meant by "fire and forget". In practice you may still
Me too. May be we should have a look and propose an improvement of the documentation. May be in the form of a "best practices" document?
Yes, I agree. Or maybe I just haven't thought about it enough. Because I avoid using it. I stick to make correct choices between using |
What do you mean, CoroutineExceptionHandler also to be avoided?
Logging is a common use case, since if something crashes, you want to be notified even if you don't need the result... |
No sorry. I only meant don't call callback from 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 indeed a common case of a good usage of 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 |
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 #61). I've gotten this to work successfully usingasync
instead oflaunch
, but I really would like to understand how to do this usinglaunch
.Given the following:
What would I need to do to make the following helper class forward errors through a callback properly?
I've tried adding a
CoroutineExceptionHandler
to various contexts up the chain, but no combination oflaunch(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 firstlaunch
, which I can log, but not re-throw in order to percolate. I'd like to bubble them up to catch them inhandleCompletion
.The text was updated successfully, but these errors were encountered: