javakotlinlogic

How handle one and more data by Kotlin.Result Chain Call and then handle result


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()
    }
}

Solution

  • 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 catches in your first code snippet, this changes all Throwables into the custom RuntimeException. If you only want to handle Exceptions, 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)
        }
    }