重新组织链接的可观察对象 [英] Re-organizing chained observables

查看:32
本文介绍了重新组织链接的可观察对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当通过table.rx.modelSelected 选择一个tableviews 行时,我有一大块链式Rx observables 被触发.

I have a pretty hefty chunk of chained Rx observables that are fired when a tableviews row is selected via table.rx.modelSelected.

我希望能够打破这个逻辑,因为我目前必须在 flatMapLatest 中执行业务逻辑,因为它是流程的第 1 步"(感觉不对),并且我必须在后续的subscribe(第2步")中执行更多的业务逻辑.这是我正在使用的代码:

I'd like to be able to break this logic up, because I'm currently having to execute business logic in flatMapLatest, because it's "Step 1" to the process (which feels wrong), and I have to execute more business logic in the subsequent subscribe ("Step 2"). Here's the code I'm using:

locationsTable.rx.modelSelected(Location.self)
    .flatMapLatest { [weak self] location -> Observable<[JobState]?> in
        guard let hubs = self?.viewModel.userInfo.authorizedHubLocations else { return .empty() }
        guard let hub = hubs.first(where: { $0.locationId == location.id }) else { return .empty() }
        guard let hubToken = hub.hubToken else { return .empty() }

        // save data in db
        self?.databaseService.persistHub(hubResult: hub, location: location)

        // make network call for the 2nd step (the subscribe)
        let networkService = NetworkService(plugins: [AuthPlugin(token: hubToken)])
        return networkService.jobStates(locationId: location.id)
    }
    .subscribe(onNext: { [weak self] jobState in
        if let jobState = jobState {
            self?.databaseService.persistJobStates(jobStates: jobState)
        }
        NavigationService.renderScreenB()
    }, onError: { error in
        Banner.showBanner(type: .error, title: "Whoops", message: "Something went wrong.")
    }).disposed(by: disposeBag)

此代码目前有效,但感觉很脏.任何有关如何清理它的建议将不胜感激.

This code currently works, but it feels dirty. Any advice on how to clean this up would be greatly appreciated.

推荐答案

您有几个独立且不同的逻辑和副作用,您正试图将它们全部塞入一个 flatMap 中.我建议将它们分解成它们的组成部分.

You have several separate and distinct bits of logic and side-effects and you are trying to stuff them all into a single flatMap. I suggest breaking them up into their component parts.

另外,你的错误逻辑不正确.如果您的网络服务发出错误消息,您的Whoops"横幅将显示,但它也会破坏您的链条,用户将无法选择其他位置.我下面的代码解决了这个问题.

Also, your error logic isn't correct. If your network service emits an error your "Whoops" banner will display, but it will also break your chain and the user won't be able to select a different location. My code below fixes this problem.

以下功能均为免费功能.由于它们不绑定到特定的视图控制器,因此可以独立使用和测试.还要注意,这些函数包含所有逻辑和系统逻辑.这使您可以无副作用地测试逻辑并促进良好的架构.还要注意它们返回 Drivers.您可以确定这些函数都不会发出会破坏链和视图控制器行为的错误.

The functions below are all free functions. Since they are not tied to a specific view controller, they can be used and tested independently. Also notice that these functions encompass all the logic and only the logic of the system. This allows you to test the logic free of side-effects and promotes good architecture. Also notice that they return Drivers. You can be sure that none of these functions will emit an error which would break the chain and the view controller's behavior.

/// Emits hubs that need to be persisted.
func hubPersist(location: ControlEvent<Location>, userInfo: UserInfo) -> Driver<(location: Location, hub: Hub)> {
    let hub = getHub(location: location, userInfo: userInfo)
        .asDriver(onErrorRecover: { _ in fatalError("no errors are possible") })
    return Driver.combineLatest(location.asDriver(), hub) { (location: $0, hub: $1) }
}

/// Values emitted by this function are used to make the network request.
func networkInfo(location: ControlEvent<Location>, userInfo: UserInfo) -> Driver<(NetworkService, Int)> {
    let hub = getHub(location: location, userInfo: userInfo)
    return Observable.combineLatest(hub, location.asObservable())
        .compactMap { (hub, location) -> (NetworkService, Int)? in
            guard let hubToken = hub.hubToken else { return nil }
            return (NetworkService(plugins: [AuthPlugin(token: hubToken)]), location.id)
        }
        .asDriver(onErrorRecover: { _ in fatalError("no errors are possible") })
}

/// shared logic used by both of the above. Testing the above will test this by default.
func getHub(location: ControlEvent<Location>, userInfo: UserInfo) -> Observable<Hub> {
    return location
        .compactMap { location -> Hub? in
            let hubs = userInfo.authorizedHubLocations
            return hubs.first(where: { $0.locationId == location.id })
    }
}

下面的函数是您的网络请求的包装器,使错误更有用.

The function below is a wrapper around your network request that makes errors more usable.

extension NetworkService {
    func getJobStates(locationId: Int) -> Driver<Result<[JobState], Error>> {
        return jobStates(locationId: locationId)
            .map { .success($0 ?? []) }
            .asDriver(onErrorRecover: { Driver.just(.failure($0)) })
    }
}

这是使用上述所有内容的视图控制器代码.它几乎完全由副作用组成.唯一的逻辑是两个守卫来检查网络请求的成功/失败.

Here is your view controller code using all of the above. It consists almost exclusively of side effects. The only logic are a couple of guards to check for success/failure of the network request.

func viewDidLoad() {
    super.viewDidLoad()

    hubPersist(location: locationsTable.rx.modelSelected(Location.self), userInfo: viewModel.userInfo)
        .drive(onNext: { [databaseService] location, hub in
            databaseService?.persistHub(hubResult: hub, location: location)
        })
        .disposed(by: disposeBag)

    let jobStates = networkInfo(location: locationsTable.rx.modelSelected(Location.self), userInfo: viewModel.userInfo)
        .flatMapLatest { networkService, locationId in
            return networkService.getJobStates(locationId: locationId)
        }

    jobStates
        .drive(onNext: { [databaseService] jobStates in
            guard case .success(let state) = jobStates else { return }
            databaseService?.persistJobStates(jobStates: state)
        })
        .disposed(by: disposeBag)

    jobStates
        .drive(onNext: { jobStates in
            guard case .success = jobStates else { return }
            NavigationService.renderScreenB()
        })
        .disposed(by: disposeBag)

    jobStates
        .drive(onNext: { jobStates in
            guard case .failure = jobStates else { return }
            Banner.showBanner(type: .error, title: "Whoops", message: "Something went wrong.")
        })
        .disposed(by: disposeBag)
}

仅供参考,以上代码使用 Swift 5/RxSwift 5.

FYI, the above code uses Swift 5/RxSwift 5.

这篇关于重新组织链接的可观察对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆