Single.zip - How to catch failed call and continue the rest of network calls?

Viewed 116

I'm making 5 parallel network calls, mocking 4 of them to succeed and one of them to fail.

The failed call makes the entire Single.zip() fail and I can't get the results of the 4 other network calls even though they have succeeded.

How can I handle the error for the single failed network call in the Single.zip() and get the results of the ones that have succeeded?

private Single<BigInteger> createNetworkCall(){
        return Single.fromCallable(() -> {
            
            return service.getBalance("validaddress").execute();
        }).subscribeOn(Schedulers.io());
}
private Single<BigInteger> createFailedNetworkCall(){
        return Single.fromCallable(() -> {
            
            return service.getBalance("invalidaddress").execute();
        }).subscribeOn(Schedulers.io());
}
private void makeParallelCalls(){
        List<Single<BigInteger>> iterable = new ArrayList<>();
        iterable.add(createNetworkCall());
        iterable.add(createNetworkCall());
        iterable.add(createNetworkCall());
        iterable.add(createNetworkCall());
        iterable.add(createFailedNetworkCall());

        Single.zip(iterable, (results) -> {
            Log.d(TAG, "makeParallelCalls: " + Arrays.toString(results));
            return results;
        }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(results-> {
                    Log.d(TAG, "onSuccess: makeParallelCalls: " + results);

                }, (exception) -> {
                    Log.e(TAG, "onError: makeParallelCalls", exception);

                });
}
2 Answers

Catch the exception and don't allow the error to interrupt the Single.zip.

For example inside your request factory, return an Optional instead of the response.

private Single<Optional<Long>> performNetworkCall(int n){
    return Single.fromCallable(() -> {
        if (n % 2 == 0) {
            throw new Exception("failed call");
        }
        return 0L;
    }).subscribeOn(Schedulers.io())
    .map(Optional::of)
    .onErrorReturnItem(Optional.empty());
    // or .onErrorReturn(exception -> Optional.empty());
}

private void makeParallelCalls(){
    List<Single<BigInteger>> iterable = new ArrayList<>();
    iterable.add(performNetworkCall(1));
    iterable.add(performNetworkCall(2));
    iterable.add(performNetworkCall(3));
    iterable.add(performNetworkCall(4));
    iterable.add(performNetworkCall(5));

    Single.zip(iterable, (results) -> {
                for (Object result : results) {
                    var optional = (Optional<Integer>)result;
                    if (optional.isEmpty()) {
                        // this one failed, no data
                    } else {
                        var response = optional.get();
                    }
                }
                return results;
            });
}

You could replace the Optional with a custom class that holds additional information, such as the exception or error information that caused the failure.

If you have recovery options, you could use .onErrorResumeNextinstead.

I'm quite new to RxJava so I don't know if there is a more better solution or if this is a really bad practice, but I used the onErrorReturn callback on the Single.

If someone has a better solution, please share so I can mark yours correct!

public static final int INVALID_USER_ADDRESS = -101;
private Single<BigInteger> createNetworkCall(){
        return Single.fromCallable(() -> {
            
            return service.getBalance("validaddress").execute();
        }).subscribeOn(Schedulers.io());
}
private Single<BigInteger> createFailedNetworkCall(){
        return Single.fromCallable(() -> {
            
            return service.getBalance("invalidaddress").execute();
        }).subscribeOn(Schedulers.io()).subscribeOn(Schedulers.io()).onErrorReturn(throwable -> {
            if(throwable instanceof IllegalArgumentException){
                return BigInteger.valueOf(INVALID_USER_ADDRESS);
            }else{
                return BigInteger.valueOf(-100);
            }
        });;
}
private void makeParallelCalls(){
        List<Single<BigInteger>> iterable = new ArrayList<>();
        iterable.add(createNetworkCall());
        iterable.add(createNetworkCall());
        iterable.add(createNetworkCall());
        iterable.add(createNetworkCall());
        iterable.add(createFailedNetworkCall());

        Single.zip(iterable, (results) -> {
            Log.d(TAG, "makeParallelCalls: " + Arrays.toString(results));
            return results;
        }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(results-> {

                    for(int i = 0; i < results.length; i++){
                        if(results[i].equals(INVALID_USER_ADDRESS)){
                            //Handle error here
                            
                        }else{
                            //Handle success value here
                            
                        }
                    }
                }, (exception) -> {
                    Log.e(TAG, "onError: makeParallelCalls", exception);

                });
}

.subscribe(results-> {

returns:

[2583397195825000000000, 2583397195825000000000, 2583397195825000000000, 2583397195825000000000, -101]

Related