如何在RxJava中先发送错误事件再返回缓存数据?
如何用RxJava在MVP架构中实现「请求失败时先展示错误再加载缓存」的需求?
刚好碰到过类似的场景,你的问题核心在于onErrorResumeNext会吞掉错误事件,导致Presenter收不到错误回调。先理清楚你的需求和约束,再给你两个可行的解决方案:
你的需求与约束
业务场景
- 优先请求服务端数据,成功后用服务端数据更新视图;
- 请求失败时(无网络、服务端错误等),先展示错误提示,再用本地缓存填充视图。
约束条件
- 全程用RxJava实现,不额外加回调;
- Presenter不能直接访问本地仓库,保持MVP的职责划分。
你已尝试的实现问题
你在Repo层用了onErrorResumeNext来切换到缓存流,但这个操作会直接吞噬错误事件,所以Presenter的错误回调永远不会被触发,自然没法展示错误提示。
你当前的Repo层代码
override fun getRepos(userName: String, page: Int, pageSize: Int):Observable<List<Repo>> { return githubRemoteService.getReposList(userName, page, pageSize) .subscribeOn(schedulersProvider.ioThread()) .flatMap { repos -> val mappedRepos = remoteResponseMapper.mapRepoResponse(repos) githubLocalService.saveRepos(mappedRepos) Observable.just(mappedRepos) } .onErrorResumeNext(Observable.just(githubLocalService.getReposList(userName, page, pageSize))) .observeOn(schedulersProvider.mainThread()) }
Presenter层代码
githubInteractor.getRepos(userName, page, pageSize).subscribe( { repos -> showReposInView(repos) }, { error -> digestAndShowErrorInView(error) // 这里永远不会被执行 } )
最优解决方案:自定义事件封装错误+缓存数据
RxJava的流规则是:要么发onNext事件,要么发onError事件,不能同时发。所以我们需要把「错误信息」和「缓存数据」打包成一个自定义事件,通过onNext发送给Presenter,这样就能同时拿到两者。
步骤1:定义密封类包装结果状态
用Kotlin的密封类(Sealed Class)来定义两种结果类型,非常适合这种有限的状态场景:
sealed class RepoResult { // 请求成功的状态,携带服务端数据 data class Success(val repos: List<Repo>) : RepoResult() // 请求失败的状态,携带错误信息和缓存数据 data class ErrorWithCache(val error: Throwable, val cachedRepos: List<Repo>) : RepoResult() }
步骤2:修改Repo层代码,返回包装后的Observable
把原来返回Observable<List<Repo>>改成返回Observable<RepoResult>,在错误分支里把错误和缓存打包:
override fun getRepos(userName: String, page: Int, pageSize: Int): Observable<RepoResult> { return githubRemoteService.getReposList(userName, page, pageSize) .subscribeOn(schedulersProvider.ioThread()) .flatMap { repos -> // 服务端请求成功:保存数据,返回Success事件 val mappedRepos = remoteResponseMapper.mapRepoResponse(repos) githubLocalService.saveRepos(mappedRepos) Observable.just(RepoResult.Success(mappedRepos)) } .onErrorResumeNext { error -> // 请求失败:获取缓存,打包成ErrorWithCache事件 val cachedRepos = githubLocalService.getReposList(userName, page, pageSize) Observable.just(RepoResult.ErrorWithCache(error, cachedRepos)) } .observeOn(schedulersProvider.mainThread()) }
步骤3:Presenter层处理不同状态
订阅的时候通过when判断状态,分别处理成功、错误+缓存的场景:
githubInteractor.getRepos(userName, page, pageSize).subscribe { result -> when(result) { is RepoResult.Success -> showReposInView(result.repos) is RepoResult.ErrorWithCache -> { // 先展示错误提示 digestAndShowErrorInView(result.error) // 再加载缓存数据 showReposInView(result.cachedRepos) } } }
这个方案完全符合你的约束:
- 全程用RxJava的流处理,没有额外回调;
- Presenter完全不接触本地仓库,只和Interactor返回的
RepoResult打交道; - 清晰分离了不同状态的处理逻辑,符合MVP的职责划分。
另一种不太推荐的方案(仅供参考)
如果不想定义密封类,可以尝试用doOnError在错误发生时先触发提示,但这个方案有个致命问题:doOnError里如果要调用视图方法,会让Repo层直接依赖视图,违反了MVP的设计原则,所以不推荐:
override fun getRepos(userName: String, page: Int, pageSize: Int): Observable<List<Repo>> { return githubRemoteService.getReposList(userName, page, pageSize) .subscribeOn(schedulersProvider.ioThread()) .flatMap { repos -> val mappedRepos = remoteResponseMapper.mapRepoResponse(repos) githubLocalService.saveRepos(mappedRepos) Observable.just(mappedRepos) } .doOnError { error -> // 切换到主线程展示错误,但这里直接调用视图方法会耦合Repo和View Observable.just(error) .observeOn(schedulersProvider.mainThread()) .subscribe { err -> view?.showError(err) } } .onErrorResumeNext { Observable.just(githubLocalService.getReposList(userName, page, pageSize)) .observeOn(schedulersProvider.mainThread()) } }
内容的提问来源于stack exchange,提问作者Amir Ziarati
相关产品推荐
相关产品推荐

