简体   繁体   中英

How to map RxSwift Observable and Result

I have a quick question:

  • I have a network request that returns Observable<Result<String, RequestError>> , let's call it requestToken
  • if this request succeeds, I want to use the String (token) to do another request that returns Observable<Result<NSDictionary, RequestError>> , let's call it requestData
  • when that second request comes back, I wanna merge the token into its dictionary
  • in the end I wanna map from 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))
        }
    })

Updated:

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)

Original:

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))
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM