Then/catch/finally semantics for coroutines


#1

then/catch/finally semantics for coroutines may feel like a step backward towards promises, however a few use cases are:

Reduce try {} catch {} boilerplate

val job : Deferred<T> = async { ... }

job.finally { it: Result<T> ->
  when (it) {
    is Result.Value -> println(it.value)
    is Result.Error -> println(it.error)
  }
}

Error Recovery

runBlocking {
  val job = async(coroutineContext) {
    delay(200)
    throw Exception("Oops")
  }

  val result = job.catch(coroutineContext) { it: Throwable ->
    println("catch: ${it.message}")  //=> catch: Oops
    "World"
  }.await()

  println("result: Hello $result")  //=>result: Hello World
}

Gimme the Result

val job : Deferred<T> = async { ... }

job.then { 
  println(it)
}

A Deferred for a Deferred makes the world…

val job : Deferred<Int> = async { 1 }

val result = job.then {  it + 1 }.await()
println(result)   //=> 2

Feedback welcome.


#2

There is the proposal for Result type:
https://youtrack.jetbrains.com/issue/KT-18608


#3

To me the idea behind coroutines is to create suspending function and write code as if it would be sequential. And one of the benefits is that exception management is simply the try-catch-finally of the language, without the need of specific operators.

If you design your API with suspending function instead of function returning jobs and deferred, then you can use simple try-catch-finally.

Here is how I would achieve the use cases you presented:

try {
	println("result: ${job()}") // job being a suspending function
} catch(e: Exception) {
	println("error: ${e.message}")
}
val result = try {
    delay(200)
    throw Exception("Oops")
} catch(e: Exception) {
    println("catch: ${it.message}")  //=> catch: Oops
    "World"
}

println("result: Hello $result")  //=>result: Hello World
job() // job being a suspending function
println(it) // then (executed only if job succeed)