Returning a Deferred<Result<T>> from a Coroutine Async method

We have recently introduced Coroutines to our codebase and have a method similar to the signature below for one of the asynchronous operations we need to perform:

private fun fetchSomethingFromServerAndReturnItsLocalPath(somethingId: String): Deferred<Result<File>>

We are experiencing problems with this approach when the Result is set to indicate failure in the resulting async Coroutine this method runs. It appears that when the Coroutine returns the exception is thrown rather the Deferred<Result> object being passed onto the parent Coroutine as we had intended.

We have tried various solutions from searching online such as using a Supervisor Job or attempting to catch the exception when we call await on our Deferred object but with no success so far.

As yet we haven’t found any mention online of returning a Result within a Deferred object however and so wondered if doing so is a suitable approach for this kind of operation? Or are there alternative implementations anyone could suggest that would allow us to return either the successful path of the item downloaded, or the failure case?

One thought is that we could provide our own sealed class to essentially mimic the use of Result here but we wondered if there was a better alternative to this.

Thanks in advance for any suggestions!

I’m no expert, but I think that it’s developer responsability to use runCatching method to wrap error before return. Here is a little example:

import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import kotlin.Result

suspend fun fetch() : File {
    throw IOException("Your request respond with an error")
}


fun fetchDeferred() : Deferred<Result<File>> {
    return GlobalScope.async {
        // You have to wrap manually your result before return
        runCatching { fetch() }
    }
}

fun main() {
    runBlocking {
        val result = fetchDeferred().await()
        when {
            result.isSuccess -> println("Yesss...${result.getOrNull()}")
            result.isFailure -> println("Noooo...${result.exceptionOrNull()?.message}")
        }
    }
}

There are still limitations on Result objects that I don’t understand yet (I have to take time to fully read the KEEP), so any further expertise is welcome :slight_smile:

Question: Is there any reason why your fetchSomethingFromServerAndReturnItsLocalPath is not a suspend function itself ?

Thanks a lot for the suggestion @alexis.manin - I will give this a try.

Regarding using suspend - I think the reason this method was implemented as returning a Deferred instead was for the case where there was already an existing request for the same item and we can therefore just return the existing Deferred object that could again be waited upon using the await() method.

I’ve done the same thing many times for the same reason, but I would return, say Deferred<File> instead of Deferred<Result<File>>, and then catch around the await.

Ok thanks @mtimmerm, that’s good to know. Yes for now I have Deferred<File?> to avoid the Fatal Exception issue so that we can detect if the item could not be downloaded and not actually available from local storage.

If request reuse is your only reason for returning Deferred, I suggest calling await within the method instead. That way you expose a simpler suspend API and limit the concurrency to inside the method.

You know that Deferred can be completed with an exception, right? The exception will be rethrown in the call to await

I didn’t actually - still very new to Coroutines. Very useful info - time to read the docs again. Thanks