Is it not possible to recover from a failed `Defer...
# coroutines
s
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
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
@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
I am not sure I understand how wrapping
Deferred
with
coroutineScope
can prevent this?
This is for a
Deferred
specific implementation
a
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
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
@simon.vergauwen Wrapping the
async
calls more specifically.
So they originate from the local scope
s
I am not in control of the scope of the
Deferred
I am wrapping.
Copy code
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
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:
Copy code
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
I'll let you know if I find a solution but it seems that it's not possible.