I have a quick question:
Observable<Result<String, RequestError>>
, let’s call it requestToken
String
(token) to do another request that returns Observable<Result<NSDictionary, RequestError>>
, let’s call it requestData
Observable<Result<String, RequestError>>
to Observable<Result<NSDictionary, RequestError>>
How can I achieve that without multiple nested levels in my code?
This is what I have today:
requestToken()
.flatMap({ result -> Observable<Result<NSDictionary, RequestError>> in
switch result {
case .success(let token):
return requestData(token: token).map({ $0.map({ $0 + ["token": token] }) })
case .failure(let error):
return Observable.of(.failure(error))
}
})
It's a detailed example, hope this may help:
enum RequestError: Error {
case unknown
}
func requestToken() -> Observable<String> {
return Observable.create { observer in
let success = true
if success {
observer.onNext("MyTokenValue")
observer.onCompleted()
} else {
observer.onError(RequestError.unknown)
}
return Disposables.create()
}
}
func requestData(token: String) -> Observable<[String: Any]> {
return Observable<[String: Any]>.create { observer in
let success = false
if success {
observer.onNext(["uid": 007])
observer.onCompleted()
} else {
observer.onError(RequestError.unknown)
}
return Disposables.create()
}
.map { (data: [String: Any]) in
var newData = data
newData["token"] = token
return newData
}
}
requestToken() // () -> Observable<String>
.flatMapLatest(requestData) // Observable<String> -> Observable<[String: Any]>
.materialize() // Observable<[String: Any]> -> Observable<Event<[String: Any]>>
.subscribe(onNext: { event in
switch event {
case .next(let dictionary):
print("onNext:", dictionary)
case .error(let error as RequestError):
print("onRequestError:", error)
case .error(let error):
print("onOtherError:", error)
case .completed:
print("onCompleted")
}
})
.disposed(by: disposeBag)
I think it's much easier to achieve it using materialize()
with less extra work:
func requestToken() -> Observable<String> { return .empty() }
func requestData(token: String) -> Observable<NSDictionary> { return .empty() }
enum RequestError: Error {}
requestToken()
.flatMapLatest(requestData)
.materialize()
.subscribe(onNext: { event in
switch event {
case .next(let dictionary):
print("onNext:", dictionary)
case .error(let error as RequestError):
print("onRequestError:", error)
case .error(let error):
print("onOtherError:", error)
case .completed:
print("onCompleted")
}
})
.disposed(by: disposeBag)
Hope this may help.
If you use the built in error system, you can save yourself from having to manually pass the error along and all the switches that would entail. You can cast the error at the end.
I would do something more like this:
// this is necessary to handle adding the token to the dictionary.
extension Dictionary {
/// An immutable version of update. Returns a new dictionary containing self's values and the key/value passed in.
func updatedValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
}
// function signatures, note that they don't return Results anymore.
func requestToken() -> Observable<String> { /*...*/ }
func requestData(withToken: String) -> Observable<[String: Any]> { /*...*/ }
requestToken().flatMapLatest {
requestData(token: $0)
.map { $0.updatedValue($0, forKey: "token") }
.map { .success($0) }
}.catchError {
Observable.just(.failure($0 as! RequestError))
}
With the above, the end result would be an Observable<Result<[String: Any], RequestError>>
just like in your case, but the error handling is much cleaner.
If you can't change the signatures of the two functions you are using then I would do this:
func throwError<T, U: Error>(result: Result<T, U>) throws -> T {
switch result {
case .success(let token):
return token
case .failure(let error):
throw error
}
}
requestToken().map {
try throwError(result: $0)
}.flatMapLatest {
requestData(token: $0)
.map { try throwError(result: $0) }
.map { $0.updatedValue($0, forKey: "token") }
}
.map { .success($0) }
.catchError {
Observable.just(.failure($0 as! RequestError))
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With