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 */ } )
四、关键总结
- 永远不要让Retrofit直接返回密封类(Either/Result),响应转换器无法处理抽象类的实例化和分支映射;
- 封装逻辑放在Repository层或工具类,统一处理所有错误场景;
- 给Result类添加map、fold等扩展函数,让结果处理更符合函数式编程风格,减少重复代码。
内容的提问来源于stack exchange,提问作者StuartDTO
相关产品推荐
相关产品推荐

