When to use coroutine vs. Result type?

#1

I’ve recently started working with Kotlin, and it seems that coroutines and the Result type both address a similar requirement (obtaining the output of a background operation).

Both features were introduced in Kotlin 1.3 - when might I choose one vs. the other?

#2

I don’t think they are related things. Result is more like a wrapper which has a success value or exception, but no other states, such as Loading. Coroutines is about the codes executes.

#3

They are definitely related. They are both used to return the result of a background operation, whether success or failure. Result seems like it should be used with traditional callback-based background tasks (like Futures), but coroutines are a more powerful alternative to futures and seem more idiomatic.

Since both features were introduced in 1.3 and coroutines appear to be more capable, I’m wondering when or if it might be preferable to use Result instead.

#4

I think callbacks are not recommend in coroutines flow, it’s a mess, but I use the Result pattern with RxJava callback, just put async result or error into a single result object. This is better than use two callbacks to handle value or error.

Something like this:

    return subscribe(
            { liveData.postValue(Result.Value(it)) },
            { liveData.postValue(Result.Error(it)) }
    )
    when (it) {
        is Result.Loading -> value = Result.Loading
        is Result.Error -> value = it
        is Result.Value -> {...}
    }
#5

Right. You wouldn’t use callbacks and coroutines together - what I’m trying to understand is when I should use one or the other.

By the way, I’m referring specifically to the Result class in the Kotlin standard library, not the result pattern in general.

#6

AFAIK Result is meant to be used mainly, when a given function can throw exception. Instead of letting the exception fly, you can try-catch it (or use runCatching) and encapsulate it into Result class.

It is not related specifically to coroutine. If your coroutine cannot fail, then there is no need for Result. If your plain function can throw, you are free to Result. For example runCatching {…} library function will return to You a Result - either of the block inside {} -> Succes or an exception, which was thrown -> Failure

#7

But any function can throw…there are no checked exceptions in Kotlin.

#8

If you haven’t seen them yet, I highly recommend watching these talks from KotlinConf 2017 and 2018:


1 Like
#9

Thanks, will do. Maybe that will help shed some light.

#10

What I tried to say - Result is not related to coroutines. Result will not help you in any way to obtain a value from an asynchronous operation.

If you want to get a value from an asynchronous coroutine, use an async {…} coroutine builder.

For much better and more thorough explanation, see presentations by Roman Elizarov mentioned above.

#11

I disagree. Swift 5 added a Result type that is basically identical to Kotlin’s Result type and is intended to be used for returning success or failure from asynchronous operations. I imagine that Kotlin’s Result class is meant to serve a similar purpose. I’d be very interested to hear the opinion of the language/platform designers on this.

#12

Here is the proposal, which lead to it being added to the std-lib


and the discussion about it

2 Likes
#13

Thank you, will take a look.

#14

Why do you limit this definition to asynchronous operations, though? The Result type is here to encapsulate success/failure in general, not just for asynchronous stuff. A synchronous operation can succeed or fail just as much as an asynchronous one, and you may or may not want to use the Result type to represent this in both cases.

It’s true that a lot of stuff that programmers usually do asynchronously require some error handling (like network calls), but it’s not the fact that they are asynchronous that make them good candidates for standardized error handling, it’s the fact that they often fail.

#15

I’m not saying that Result is limited to asynchronous operations. I just imagine that is one of the intended use cases for it.

#16

It is one of its purposes I guess, but this class is really about handling errors, while coroutines are about organizing asynchronous calls and concurrent computations, so I don’t really see the overlap in purpose that you seem to point out here.

You could really use Result as the return type of a suspending function just as much as using it as the return type of a regular function. Whether this is a good idea is debatable, but I believe that using coroutines and using Result are almost orthogonal concerns.

#17

Maybe you’re confusing Result with Deferred<T> here?

Deferred<T> definitely is similar to Java’s Future or JS’s Promise, and is entirely related to async stuff. And it would be an interesting topic to decide when to use a Deferred<T> vs a suspend function returning T.

Or maybe you were considering the combination of Deferred / Future with Result?

1 Like
#18

You don’t really need Result in a synchronous method, though. Synchronous methods can just throw an exception, whereas async methods can’t. That’s why Result is applicable to background tasks (and why I think it is related to coroutines).

#19

I wasn’t aware of Deferrred. It seems conceptually similar to Result in that it encapsulates either a success or failure result.

I guess the question is - when would you ever use Result in a synchronous operation? As I mentioned earlier, a synchronous method is either going to return a successful result, or it is going to throw. You don’t really need Result for that, since you already get everything you need from the language.

However, asynchronous operations do need some sort of construct to propagate the result (success or failure) back to the calling thread. It seems like Result is a natural fit for that - but then again, so is Deferred, and so are coroutines. Hence, my original question - when would I want to use one vs. another?

#20

Not really. Deferred represents a pending operation’s result. When you call deferred.await(), it waits for the end of the corresponding async operation to finish (it effectively suspends the current coroutine), and then either returns the operation’s return value, or throws the exception that the async operation has thrown (if any).

I don’t know the details of Result, but I believe its purpose is to represent a finished operation’s result. Getting information out of it will return immediately with either the success or failure data.

Actually, there is a use case in the proposal for Result itself where Deferred and Result are used together. You can see there that Result is used to replace the exception-throwing behaviour by an object representation (using runCatching), while Deferred is used to keep a reference to a running job.

You don’t really need Result in a synchronous method, though. Synchronous methods can just throw an exception, whereas async methods can’t.

Well you can also throw exceptions from async operations using Deferred.await() mentioned above, since what matters is the moment we use the result of the async operation anyway.

I believe Result is really about replacing/wrapping exceptions in cases where it’s more comfortable to use. And this includes some synchronous behaviours as well, like error aggregation on operations that process collections for instance.

1 Like