https://kotlinlang.org logo
Title
s

simon.vergauwen

01/21/2019, 1:12 PM
Is it not possible to recover from a failed
Deferred<A>
? I was expecting to be able to define a
handleErrorWith
operator that wraps an existing
Deferred
to recover from errors but the program results in the original error.
a

Allan Wang

01/21/2019, 2:25 PM
I think it’s because you need to try catch within the async creation rather than just around await, as a failed async will cancel the parent
l

louiscad

01/21/2019, 3:03 PM
@simon.vergauwen You need a local wrapping
coroutineScope { ... }
, and catch exceptions outside of it for this to work properly. That is structured concurrency. Also, most of the time, you won't need
Deferred
nor
async
and
await
because
suspend fun
,
withContext
and
launch
satisfy most use cases.
s

simon.vergauwen

01/21/2019, 3:11 PM
I am not sure I understand how wrapping
Deferred
with
coroutineScope
can prevent this?
This is for a
Deferred
specific implementation
a

Allan Wang

01/21/2019, 3:14 PM
Is it possible that you take in suspended functions and have
handleErrorWith
create the deferred executions? Otherwise I’m not sure there’s anything you can do to stop parent cancellation unless the deferred creator implements it
s

simon.vergauwen

01/21/2019, 3:15 PM
That is not always the case, if a user decided to interop with
Deferred
there is no way to know what code is underneath.
l

louiscad

01/21/2019, 3:19 PM
@simon.vergauwen Wrapping the
async
calls more specifically.
So they originate from the local scope
s

simon.vergauwen

01/21/2019, 3:19 PM
I am not in control of the scope of the
Deferred
I am wrapping.
fun <A> CoroutineScope.handleErrorWith(fa: Deferred<A>, f: (Throwable) -> Deferred<A>): Deferred<A> =
  async(start = CoroutineStart.LAZY) {
    try {
        coroutineScope {
          fa.await()
        }
    } catch (e: Throwable) {
      f(e).await()
    }
  }
a

Allan Wang

01/21/2019, 3:23 PM
It works if you add
SupervisorJob
to your caller context (which you can’t control)
I don’t see how you’d be able to do this without enforcing a constraint like that since the default behaviour is to cancel other coroutines. I’d be interested in hearing a solution though
👍 1
Reference:
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.plus
import kotlinx.coroutines.runBlocking
import kotlin.test.Test

class Test {

    fun <A> CoroutineScope.handleErrorWith(fa: Deferred<A>, f: (Throwable) -> Deferred<A>): Deferred<A> =
        async(start = CoroutineStart.LAZY) {
            try {
                fa.await()
            } catch (e: Throwable) {
                f(e).await()
            }
        }

    @Test
    fun t() = runBlocking {
        val result =
            with(CoroutineScope(Dispatchers.Default) + SupervisorJob()) {
                handleErrorWith(async(start = CoroutineStart.LAZY) {
                    throw RuntimeException("Boom!")
                }) {
                    async { it.message }
                }

            }

        val r = result.await() //threw RuntimeException("Boom!") but expected String of value "Boom!"
        println(r)
    }
}
s

simon.vergauwen

01/21/2019, 3:42 PM
I'll let you know if I find a solution but it seems that it's not possible.