我目前正试图把精力放在RxJava上,但我在以优雅的方式处理服务调用异常方面遇到了一点麻烦。
基本上,我有一个(改型)服务,它返回一个可观察的
。ServiceResponse
的定义如下:
public class ServiceResponse {
private int status;
private String message;
private JsonElement data;
public JsonElement getData() {
return data;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
}
现在,我想要的是将该泛型响应映射到data JsonElement字段中包含的列表
(我假设您不关心account
对象的外观,所以我不会用它污染文章)。下面的代码对于success案例非常有效,但是我找不到一个很好的方法来处理我的API异常:
service.getAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<ServiceResponse, AccountData>() {
@Override
public AccountData call(ServiceResponse serviceResponse) {
// TODO: ick. fix this. there must be a better way...
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
switch (responseType) {
case SUCCESS:
Gson gson = new GsonBuilder().create();
return gson.fromJson(serviceResponse.getData(), AccountData.class);
case HOST_UNAVAILABLE:
throw new HostUnavailableException(serviceResponse.getMessage());
case SUSPENDED_USER:
throw new SuspendedUserException(serviceResponse.getMessage());
case SYSTEM_ERROR:
case UNKNOWN:
default:
throw new SystemErrorException(serviceResponse.getMessage());
}
}
})
.map(new Func1<AccountData, List<Account>>() {
@Override
public List<Account> call(AccountData accountData) {
Gson gson = new GsonBuilder().create();
List<Account> res = new ArrayList<Account>();
for (JsonElement account : accountData.getAccounts()) {
res.add(gson.fromJson(account, Account.class));
}
return res;
}
})
.subscribe(accountsRequest);
有没有更好的办法做到这一点?这是有效的,onError将触发我的观察者,我将收到我抛出的错误,但这绝对不是我做的正确的。
提前道谢!
编辑:
我希望有一个可以从UI调用的类(例如,一个活动,或片段,或其他)。该类将使用观察者
<list
作为参数,如下所示:
public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) {
...
}
该方法将返回一个订阅,当UI被分离/销毁/等等时,该订阅可以被取消。
参数化的观察者将处理通过帐户列表的成功响应的onNext。OnError将处理任何异常,但也会传递任何API异常(例如,如果响应状态!=200,我们将创建一个Throwable并将其传递给OnError)。理想情况下,我不想只是“抛出”异常,我想直接将它传递给观察者。我看到的所有例子都是这样做的。
@Singleton
public class AccountsDatabase {
private AccountsService service;
private List<Account> accountsCache = null;
private PublishSubject<ServiceResponse> accountsRequest = null;
@Inject
public AccountsDatabase(AccountsService service) {
this.service = service;
}
public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) {
ObserverWrapper observerWrapper = new ObserverWrapper(observer);
if (accountsCache != null) {
// We have a cached value. Emit it immediately.
observer.onNext(accountsCache);
}
if (accountsRequest != null) {
// There's an in-flight network request for this section already. Join it.
return accountsRequest.subscribe(observerWrapper);
}
if (accountsCache != null && !forceRefresh) {
// We had a cached value and don't want to force a refresh on the data. Just
// return an empty subscription
observer.onCompleted();
return Subscriptions.empty();
}
accountsRequest = PublishSubject.create();
accountsRequest.subscribe(new ObserverWrapper(new EndObserver<List<Account>>() {
@Override
public void onNext(List<Account> accounts) {
accountsCache = accounts;
}
@Override
public void onEnd() {
accountsRequest = null;
}
}));
Subscription subscription = accountsRequest.subscribe(observerWrapper);
service.getAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(accountsRequest);
return subscription;
}
static class ObserverWrapper implements Observer<ServiceResponse> {
private Observer<List<Account>> observer;
public ObserverWrapper(Observer<List<Account>> observer) {
this.observer = observer;
}
@Override
public void onCompleted() {
observer.onCompleted();
}
@Override
public void onError(Throwable e) {
observer.onError(e);
}
@Override
public void onNext(ServiceResponse serviceResponse) {
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
switch (responseType) {
case SUCCESS:
Gson gson = new GsonBuilder().create();
AccountData accountData = gson.fromJson(serviceResponse.getData(), AccountData.class);
List<Account> res = new ArrayList<>();
for (JsonElement account : accountData.getAccounts()) {
res.add(gson.fromJson(account, Account.class));
}
observer.onNext(res);
observer.onCompleted();
break;
default:
observer.onError(new ApiException(serviceResponse.getMessage(), responseType));
break;
}
}
}
}
请阅读下面我添加了一个编辑。
使用RXJava在action/func/observer中抛出是完全正确的。该异常将由框架直接传播给您的观察者。如果您仅限于调用onError,那么您就会扭曲自己来实现这一点。
话虽如此,一个建议是简单地移除这个包装,并在service.getAccount中添加一个简单的验证操作...观测链。
以下是使用我的建议简化的loadAccount方法。
public Subscription loadAccounts(Observer<List<Account>> observer, boolean forceRefresh) {
if (accountsCache != null) {
// We have a cached value. Emit it immediately.
observer.onNext(accountsCache);
}
if (accountsRequest != null) {
// There's an in-flight network request for this section already. Join it.
return accountsRequest.subscribe(observer);
}
if (accountsCache != null && !forceRefresh) {
// We had a cached value and don't want to force a refresh on the data. Just
// return an empty subscription
observer.onCompleted();
return Subscriptions.empty();
}
accountsRequest = PublishSubject.create();
accountsRequest.subscribe(new EndObserver<List<Account>>() {
@Override
public void onNext(List<Account> accounts) {
accountsCache = accounts;
}
@Override
public void onEnd() {
accountsRequest = null;
}
});
Subscription subscription = accountsRequest.subscribe(observer);
service.getAccounts()
.doOnNext(new ValidateServiceResponseOrThrow())
.map(new MapValidResponseToAccountList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(accountsRequest);
return subscription;
}
private static class ValidateResponseOrThrow implements Action1<ServiceResponse> {
@Override
public void call(ServiceResponse response) {
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
if (responseType != SUCCESS)
throw new ApiException(serviceResponse.getMessage(), responseType));
}
}
private static class MapValidResponseToAccountList implements Func1<ServiceResponse, List<Account>> {
@Override
public Message call(ServiceResponse response) {
// add code here to map the ServiceResponse into the List<Accounts> as you've provided already
}
}
编辑:除非有人另有说法,否则我认为使用FlatMap返回错误是最好的做法。我在过去曾抛出过Action的异常,但我不认为这是推荐的方法。
如果使用FlatMap,将有一个更清晰的异常堆栈。如果从操作内部抛出,异常堆栈实际上将包含rx.exceptions.OnErrorThrowable$OnNextValue
异常,这并不理想。
让我使用flatMap来演示上面的示例。
private static class ValidateServiceResponse implements rx.functions.Func1<ServiceResponse, Observable<ServiceResponse>> {
@Override
public Observable<ServiceResponse> call(ServiceResponse response) {
ResponseTypes responseType = ResponseTypes.from(serviceResponse.getStatus());
if (responseType != SUCCESS)
return Observable.error(new ApiException(serviceResponse.getMessage(), responseType));
return Observable.just(response);
}
}
service.getAccounts()
.flatMap(new ValidateServiceResponse())
.map(new MapValidResponseToAccountList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(accountsRequest);
正如你所看到的,差别是细微的。ValidateServiceResponse
现在实现了Func1
而不是Action1
并且我们不再使用Throw
关键字。我们改用observable.error(new Throwable)
。我相信这更符合预期的Rx合同。
我正在使用WebAPI2.0创建我自己的项目。我的Api包含添加、获取和预订产品的功能。 我想处理异常,但有一些问题我有点困惑。 我有一个控制器:与动作方法。 现在,如果产品已经存在,那么数据访问层将抛出 我想处理这个问题,并像这样回复: 返回HTTP状态代码200是否合理 如果不向控制器添加,我怎么做,因为我认为这不是正确的方法 谢谢
考虑以下示例: 这将输出从1到5的数字,然后打印异常。 我想要实现的是使观察器保持订阅状态,并在抛出异常后继续运行,即打印从1到10的所有数字。 我尝试过使用和其他各种错误处理操作符,但正如文档中所述,它们的目的是处理可观察对象本身发出的错误。 最直接的解决方案是将的整个主体包装成一个try-catch块,但对我来说这听起来不是一个好的解决方案。在类似的Rx中。NET问题,提出的解决方案是制作一个
我主要是为技术精明的人编写一个小工具,例如程序员、工程师等,因为这些工具通常是快速的,随着时间的推移,我知道会有未处理的异常,用户不会介意。我希望用户能够向我发送回溯,这样我就可以检查发生了什么,并可能改进应用程序。 我通常做wxPython编程,但我最近做了一些Java。我已经将
我正在做我的一项任务。这相当直截了当。包含单个输入的HTML表单被提交到Servlet,Servlet获取参数,基于参数创建消息,将消息作为属性添加到请求,并使用requestdispatcher转发到jsp以显示消息。 我有一个要求,如果参数丢失,我需要显示一个错误页面。问题是我不能显式地检查null,也不能使用try/catch块。我的猜测是,目标是在web中定义错误页面。xml页面来处理特定
我遇到的问题是,当我的中出现异常时,没有处理异常。我希望它能在那里被捕获,就像它使用旧的ASP.NET Web API一样。 那么,如何捕获所有应用程序异常以及操作筛选器中的任何异常呢?
问题内容: 考虑以下示例: 这将输出从1到5的数字,然后打印异常。 我要实现的是使观察者保持订阅状态,并在引发异常后继续运行,即打印从1到10的所有数字。 我曾尝试使用和其他各种错误处理运算符,但是,正如文档中所述,它们的目的是处理可观察对象自身发出的错误。 最直接的解决方案是将整个过程包装到try- catch块中,但这对我来说似乎不是一个好的解决方案。在类似的Rx.NET问题中,提出的解决方案