I has a usage by Kotlin.Result chain call.
fun getA():A{
...
}
fun getB(a:A):B{
...
}
fun getC(b:B):C{
...
}
there function may throw any Exception at all. I want do this:
try {
val a = try {
getA()
}catch (e:Exception){
throw RuntimeException("getA is Error")
}
val b = try {
getB(a)
}catch (e:Exception){
throw RuntimeException("getB is Error")
}
val c = try {
getC(b)
}catch (e:Exception){
throw RuntimeException("getC is Error")
}.
print("c is $c")
}catch (e:Exception){
print("error: ${e.message}")
}
I want Chain call like this:
doSomeThing("getA is Error") {
getA()
}.doSomeThing("getB is Error") {
getB(it)
}.doSomeThing("getC is Error") {
getC(it)
}.onSuccess {
print("c is $it")
}.onFailure {
print("error: ${it.message}")
}
This inspiration comes from Kotlin.Result:
runCatching {
getA()
}.onSuccess {
print("c is $it")
}.onFailure {
print("error: ${it.message}")
}
Notmal Step : step1 -> step2 -> step 3 -> success
when throw exception: stepX throw exception -> recover exception -> failure.
hard point is result.recover will recover my exception and wrong exception.
My question is How can use Result or Other do this. Is this form of code worth using or will this code writing pattern have a negative impact
2025-5-10 Edit,logic from @Sweeper,I chage something.
inline fun <R> runCatchingRecover(message: String, block: () -> R): Result<R> =
runCatching(block).recover { throw RuntimeException(message, it) }
inline fun <T, R> T.runCatchingRecover(message: String, block: T.() -> R): Result<R> =
runCatching(block).recover { throw RuntimeException(message, it) }
inline fun <T, R> Result<R>.mapCatchingRecover(message: String, block: R.() -> T): Result<T> {
return mapCatching {
runCatchingRecover(message) { it.block() }.getOrThrow()
}
}
It seems like you want a "map
and then rethrow an RuntimeException
with a custom message" operation. You can write such an extension function:
inline fun <T> Result<T>.mapException(block: (Throwable) -> Throwable) =
exceptionOrNull()?.let {
Result.failure(block(it))
} ?: this
inline fun <T> runCatchingCustomMessage(message: String, block: () -> T) =
runCatching(block).mapException { RuntimeException(message) }
inline fun <T, V> Result<T>.mapCatchingCustomMessage(message: String, block: (T) -> V) =
mapCatching {
runCatchingCustomMessage(message) { block(it) }.getOrThrow()
}
The getOrThrow
+ mapCatching
combination essentially acts as a "flat map" or "bind", but it also changes the exception stack trace. If that is undesirable, you can write a proper flatMap
like this:
// from: https://stackoverflow.com/a/79450797/5133585
inline fun <T, R> Result<T>.flatMap(f: (T) -> Result<R>): Result<R> = when {
isSuccess -> (getOrNull() as T).let(f)
isFailure -> this as Result<R>
else -> {
throw IllegalStateException("Unknown state")
}
}
Usage:
runCatchingCustomMessage("get A is error") { getA() }
.mapCatchingCustomMessage("get B is error") { getB(it) }
.mapCatchingCustomMessage("get C is error") { getC(it) }
.onSuccess { println("c is $it") }
.onFailure { println("error: ${it.message}") }
Unlike the catch
es in your first code snippet, this changes all Throwable
s into the custom RuntimeException
. If you only want to handle Exception
s, you will also need to write your own versions of runCatching
and mapCatching
, e.g.
inline fun <R> runCatchingOnlyExceptions(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (e: Exception) {
Result.failure(e)
}
}