Rx seem a bit fragile in that it closes down an entire chain if one single thing doesn't work. That has become a real problem in my code as I have a chain that requests parameters through ble
. First we ask for ids
, then definitions
which is sort of mapping min and max values, lastly it asks for the actual parameters
:
override func getParameters() -> Single<[ParameterModel?]> {
parameterCounter = 0
parameterDefinitionCounter = 0
return getParamterIds().do(onSuccess: { [weak self] values in
self?.numberOfParameterIds = Float(values?.count ?? 0)
})
.flatMap { ids in
Single.zip(ids!.compactMap { self.getParamterDefinition(id: $0) }) }
.flatMap { parameters in
Single.zip(parameters.compactMap { self.getParameter(id: $0!.id) }) }
}
So if we get an array
with 30 parameter ids
, it goes into getParamterDefinition(id: $0)
. And if it fails on a single one of those, which it does, the whole thing closes down and self.getParameter(id: $0!.id)
is never run. So even though 29 parameters
pass through getParamterDefinition(id: $0)
nothing is passed to self.getParameter(id: $0!.id)
.
How do I recover from an error and keep going in the chain so that those that were successful in getParamterDefinition(id: $0)
gets passed to self.getParameter(id: $0!.id)
and gets displayed to the user?
*** UPDATE *** This is the final result for anyone interested in solving issues like these:
override func getParameters() -> Single<[ParameterModel?]> {
parameterCounter = 0
parameterDefinitionCounter = 0
func getFailedParameter(id: Int) -> ParameterModel {
return ParameterModel(id: id, name: String(format: "tech_app_failed_getting_parameter".localized(), "\(id)"), min: 2000,
max: 21600000, defaultValue: 2500, value: 2500,
unit: "ms", access: 0, freezeFlag: 0,
multiplicator: 1, operatorByte: 1, brand: 0,
states: nil, didFailRetrievingParameter: true)
}
return getParamterIds().do(onSuccess: { [weak self] values in
self?.numberOfParameterIds = Float(values?.count ?? 0)
}).catchError { _ in return Single.just([]) }
.flatMap { ids in
Single.zip(ids!.compactMap { id in
self.getParamterDefinition(id: id).catchError { [weak self] _ in
self?.parameterErrorStatusRelay.accept(String(format: "tech_app_parameter_definition_error_status".localized(), "\(id)"))
return Single.just(getFailedParameter(id: id))
}
})
}
.flatMap { parameters in
Single.zip(parameters.compactMap { parameter in
guard let parameter = parameter, !(parameter.didFailRetrievingParameter) else {
return Single.just(parameter)
}
return self.getParameter(id: parameter.id).catchError { [weak self] _ in
self?.parameterErrorStatusRelay.accept(String(format: "tech_app_parameter_error_status".localized(), "\(parameter.id)"))
return Single.just(getFailedParameter(id: parameter.id))
}
})
}
}
You should use the Catch
methods to handle errors, you can use these to stop your sequence from terminating when an error event occurs.
A simple example that just ignores any errors would be to return nil whenever your getParamterDefinition
observable emits an error:
override func getParameters() -> Single<[ParameterModel?]> {
return getParameterIds()
.do(onSuccess: { [weak self] values in
self?.numberOfParameterIds = Float(values?.count ?? 0)
})
.flatMap { ids in
Single.zip(
ids!.compactMap {
self.getParameterDefinition(id: $0)?
.catchAndReturn(nil)
}
)
}
.flatMap { parameters in
Single.zip(
parameters.compactMap { parameter in
parameter.flatMap { self.getParameter(id: $0.id) }
}
)
}
}