在不影响发布者类型的情况下向发布者转换链添加额外的逻辑

Posted

技术标签:

【中文标题】在不影响发布者类型的情况下向发布者转换链添加额外的逻辑【英文标题】:Add additional logic to chain of publisher transformation without affecting Publisher type 【发布时间】:2021-01-21 16:33:12 【问题描述】:

Kotlin 的协程提供了编写非常扁平的代码的能力。我正在尝试将一些异步 ios 代码转换为利用 Combine 来将其展平。

Kotlin 看起来像:

private suspend fun getData(
        networkCall: Boolean = true,
        networkRequest: suspend () -> Either<FieldError, List<JobModel>>,
        localRequest: suspend () -> Either<FieldError, List<JobModel>>,
        cache: suspend (data: List<JobModel>) -> Unit
): Either<FieldError, List<JobModel>> 
    // getting of Jobs and passing in pair if result was from network
    var resultNetwork = false
    var result: Either<FieldError, List<JobModel>> = Left(GenericError)
    if (!networkCall) 
        result = localRequest()
    
    // if it was not a network call and failed we will force network call
    if (networkCall || result.isLeft()) 
        resultNetwork = true
        result = networkRequest()
    

    if (result is Either.Right && resultNetwork) 
        cache(result.b)
    

    return result


Swift WIP 看起来像:

public func getData(isNetworkCall: AnyPublisher<Bool, Error>,
                    networkRequest: AnyPublisher<[Job], Error>,
                    localRequest: AnyPublisher<[Job], Error>,
                    cache: ([Job]) -> AnyPublisher<Void, Error>) -> AnyPublisher<[Job], Error>? 
    
    let getJobsRequest =  isNetworkCall.flatMap  (isCall) in
        return isCall
            ? networkRequest
            //.also  jobs in cache(jobs) 
            .catch _ in return localRequest 
            .eraseToAnyPublisher()
            : localRequest
    .eraseToAnyPublisher()
    
    return getJobsRequest

如何将此缓存数据的逻辑添加为此 AnyPublisher 的一部分?我想让它缓存在这个逻辑转换中。理想情况下,可能有一个 also 函数在订阅者完成事务时附加逻辑。

解决方案:

private func getData(isNetworkCall: AnyPublisher<Bool, Error>,
                         networkRequest: AnyPublisher<[Job], Error>,
                         localRequest: AnyPublisher<[Job], Error>,
                         cache: @escaping ([Job]) -> AnyPublisher<Void, Error>) -> AnyPublisher<[Job], Error> 
    
    // Sequence of steps for when we should do a network call
    let networkCallFlow = networkRequest
        .flatMap  jobs in // cache jobs from network
            cache(jobs)
                .replaceError(with: ()) // fire and forget cache, replace error with Void to continue
                .map  _ in jobs  // need to give back jobs to flatMap
                .setFailureType(to: Error.self) // match failure type
        
        .catch  _ in localRequest  // return local if network fails
        .eraseToAnyPublisher()
    
    // Sequence of steps for when we should get from local
    let localCallFlow = localRequest
        .catch  _ in networkCallFlow  // do network flow if local call fails
        .eraseToAnyPublisher()

    return isNetworkCall
        .flatMap  $0 ? networkCallFlow : localCallFlow 
        .eraseToAnyPublisher()

【问题讨论】:

【参考方案1】:

你可以用flatMap链接它。

如果 cache 也是 AnyPublisher&lt;[Job], Error&gt; 会更容易,那么您可以拥有以下内容:

return isCall
    ? networkRequest
        .flatMap  jobs in cache(jobs) 
        .catch _ in return localRequest 
        .eraseToAnyPublisher()
    : localRequest

否则,您需要将其Void 返回值映射回jobs

return isCall
    ? networkRequest
        .flatMap  jobs in 
            cache(jobs).map  _ in jobs 
        
        .catch _ in return localRequest 
        .eraseToAnyPublisher()
    : localRequest

【讨论】:

这带来的问题是 - 当cache() 抛出错误时会发生什么? - 理想情况下,它应该传递网络的结果,但在这种情况下,它会抛出本地的任何内容,即使该数据由于缓存失败而已过时。我正在寻找一种本质上触发并忘记cache(...) 的方法,并且如果进行了网络调用,则始终传递网络的结果。这适用于我们希望 cache(...) 按预期工作但在其他情况下会出现问题的情况。 所以,如果你想忽略cache 抛出的错误,你可以这样做:cache(jobs).replaceError(with: jobs).map _ in jobs 天才!由于缓存是无效的,它将是cache(jobs).replaceError(with: ()).map _ in jobs 我将发布代码并在完全测试后接受。

以上是关于在不影响发布者类型的情况下向发布者转换链添加额外的逻辑的主要内容,如果未能解决你的问题,请参考以下文章

在不使用 reloadData 的情况下向 UITableView 添加一行

如何在不锁定表的情况下向 Postgres 中的 ENUM 添加新值?

Jackson:如何在不修改 POJO 的情况下向 JSON 添加自定义属性

在不知道架构名称的情况下向组角色添加权限

如何在不阻止 textView 触摸的情况下向所有 UITextView 添加一个 UIGestureRecognizer

在不重新加载的情况下向 div 添加/删除内容