You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Retrofit响应封装Either/Result类遇实例化异常及实现疑问

解决Retrofit响应封装:从Either报错到Result正确转换

一、为什么直接让Retrofit返回Either会报错?

你遇到的Gson实例化异常,核心原因是Retrofit的响应转换器(比如Gson)无法处理密封抽象类的反序列化:

  • Either是密封抽象类,Gson不能直接实例化它;
  • 更关键的是,Retrofit的转换器不知道什么时候该生成Left(错误)、什么时候生成Right(成功)——它只会在HTTP 2xx时尝试解析响应体,非2xx时要么抛出异常,要么返回Response<T>,不会自动映射到你的密封类子类。

所以正确的思路是:让Retrofit返回原生的Call<T>或suspend函数的直接返回值,再在Repository层或专门的工具类里封装成你的Either/Result类型。

二、正确实现Call到Result的转换

针对你定义的Result密封类,我们需要覆盖所有可能的错误场景:网络异常、HTTP错误码、空响应体、成功响应。这里给你一个完善的转换工具函数:

import retrofit2.Call
import retrofit2.Response
import java.net.SocketTimeoutException

inline fun <T> executeCall(call: Call<T>): Result<T> {
    return try {
        val response = call.execute()
        when {
            response.isSuccessful -> {
                val body = response.body()
                if (body != null) {
                    Result.Success(body)
                } else {
                    // 处理2xx但响应体为空的情况(比如204 No Content)
                    Result.Error(Throwable("Response body is empty"))
                }
            }
            response.code() == 401 -> {
                Result.Unauthorized(Exception("Unauthorized: ${response.code()}"))
            }
            else -> {
                Result.Error(Exception("HTTP Error: ${response.code()} - ${response.message()}"))
            }
        }
    } catch (e: Exception) {
        // 区分不同类型的网络异常
        when (e) {
            is SocketTimeoutException -> Result.Timeout(e)
            else -> Result.Error(e)
        }
    }
}

在RepositoryImpl里使用这个函数就很简单了:

class RepositoryImpl(private val apiService: ApiService) {
    fun fetchSomeData(): Result<POJO> {
        // apiService.pews() 返回的是Call<POJO>
        return executeCall(apiService.pews())
    }
}

如果你用的是Retrofit 2.6+支持的挂起函数,还可以写更简洁的suspend版本:

import retrofit2.HttpException
import java.net.SocketTimeoutException

suspend inline fun <T> executeSuspend(crossinline block: suspend () -> T): Result<T> {
    return try {
        Result.Success(block())
    } catch (e: Exception) {
        when (e) {
            is SocketTimeoutException -> Result.Timeout(e)
            is HttpException -> {
                // 处理HTTP错误码
                if (e.code() == 401) {
                    Result.Unauthorized(e)
                } else {
                    Result.Error(e)
                }
            }
            else -> Result.Error(e)
        }
    }
}

// Repository中使用
class RepositoryImpl(private val apiService: ApiService) {
    suspend fun fetchSomeData(): Result<POJO> {
        // apiService.pews() 是suspend函数,返回POJO
        return executeSuspend { apiService.pews() }
    }
}

三、优化Result的处理体验:添加fold函数

你之前用fold处理Either,同样可以给Result添加fold函数,让结果处理更优雅、更函数式:

sealed class Result<T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Unauthorized(val exception: Exception) : Result<Nothing>()
    data class Timeout(val exception: Exception) : Result<Nothing>()
    data class Error(val exception: Exception) : Result<Nothing>()

    // 添加fold函数,统一处理所有结果分支
    fun <R> fold(
        onSuccess: (T) -> R,
        onUnauthorized: (Exception) -> R,
        onTimeout: (Exception) -> R,
        onError: (Exception) -> R
    ): R {
        return when (this) {
            is Success -> onSuccess(data)
            is Unauthorized -> onUnauthorized(exception)
            is Timeout -> onTimeout(exception)
            is Error -> onError(exception)
        }
    }
}

使用时就不用写冗长的when语句了:

val result = repository.fetchSomeData()
result.fold(
    onSuccess = { pojo -> /* 更新UI、保存数据等成功逻辑 */ },
    onUnauthorized = { e -> /* 跳转到登录页、提示重新授权 */ },
    onTimeout = { e -> /* 提示用户网络超时,建议重试 */ },
    onError = { e -> /* 通用错误提示,比如Toast */ }
)

四、关键总结

  1. 永远不要让Retrofit直接返回密封类(Either/Result),响应转换器无法处理抽象类的实例化和分支映射;
  2. 封装逻辑放在Repository层或工具类,统一处理所有错误场景;
  3. 给Result类添加map、fold等扩展函数,让结果处理更符合函数式编程风格,减少重复代码。

内容的提问来源于stack exchange,提问作者StuartDTO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:18:45