ios - Swift 仅针对某些错误类型组合重试

标签 ios swift combine

我有一个自定义管道,我想对一些可恢复的错误代码进行 3 次重试,另外我想为可恢复错误添加一些短延迟。任何人都知道我该怎么做?

func createRequest(for message: Message) -> AnyPublisher<ResponseMessage, Error> {
    Future<ResponseMessage, Error> { promise in
        .....   
    }
    .tryCatch({ error -> AnyPublisher<ResponseMessage, Error> in
        // If error is a recoverable error retry, otherwise fail directly
        if case let MessageBusError.messageError(responseError) = error {
            if responseError.isRecoverable {
                // Make a next attempt only for recoverable error
                throw error
            }
        }
            //Should fail directly if the error code is not recoverable
        return Fail<ResponseMessage, Error>(error: error)
               .eraseToAnyPublisher()

    })
    .retry(3)
    .eraseToAnyPublisher()
}

最佳答案

基本上,您需要一个 retryIf运算符,因此您可以提供一个闭包来告诉Combine 哪些错误应该重试,哪些不应该重试。我不知道有这样的操作符,但为自己构建一个并不难。
惯用的方式是扩展 Publishers命名空间为您的运算符添加一个新类型,然后扩展 Publisher添加对该运算符的支持,以便您可以将它与其他运算符链接起来。
实现可能如下所示:

extension Publishers {
    struct RetryIf<P: Publisher>: Publisher {
        typealias Output = P.Output
        typealias Failure = P.Failure
        
        let publisher: P
        let times: Int
        let condition: (P.Failure) -> Bool
                
        func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
            guard times > 0 else { return publisher.receive(subscriber: subscriber) }
            
            publisher.catch { (error: P.Failure) -> AnyPublisher<Output, Failure> in
                if condition(error)  {
                    return RetryIf(publisher: publisher, times: times - 1, condition: condition).eraseToAnyPublisher()
                } else {
                    return Fail(error: error).eraseToAnyPublisher()
                }
            }.receive(subscriber: subscriber)
        }
    }
}

extension Publisher {
    func retry(times: Int, if condition: @escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> {
        Publishers.RetryIf(publisher: self, times: times, condition: condition)
    }
}
用法:
func createRequest(for message: Message) -> AnyPublisher<ResponseMessage, Error> {
    Deferred {
        Future<ResponseMessage, Error> { promise in
            // future code
        }        
    }
    .retry(times: 3) { error in
        if case let MessageBusError.messageError(responseError) = error, responseError.isRecoverable {
            return true
        }
        return false
    }
    .eraseToAnyPublisher()
}
请注意,我包裹了您的 FutureDeferred一,否则retry运算符将毫无意义,因为闭包不会被多次执行。有关该行为的更多详细信息,请访问:Swift. Combine. Is there any way to call a publisher block more than once when retry? .

或者,您可以写 Publisher像这样的扩展:
extension Publisher {
    func retry(_ times: Int, if condition: @escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> {
        Publishers.RetryIf(publisher: self, times: times, condition: condition)
    }
    
    func retry(_ times: Int, unless condition: @escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> {
        retry(times, if: { !condition($0) })
    }
}
,它启用了一些时髦的东西,如下所示:
extension Error {
    var isRecoverable: Bool { ... }
    var isUnrecoverable: Bool { ... }
}

// retry at most 3 times while receiving recoverable errors
// bail out the first time when encountering an error that is
// not recoverable
somePublisher
    .retry(3, if: \.isRecoverable)

// retry at most 3 times, bail out the first time when
// an unrecoverable the error is encountered
somePublisher
    .retry(3, unless: \.isUnrecoverable)
甚至更时髦的 ruby 风格:
extension Int {
    var times: Int { self }
}

somePublisher
    .retry(3.times, unless: \.isUnrecoverable)

关于ios - Swift 仅针对某些错误类型组合重试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64938442/

相关文章:

ios - SwiftUI:在 View 之间传递 ObservableObject 时,表达式类型在没有更多上下文的情况下不明确

ios - 点击手势出现问题

ios - 删除 dealloc 上对象的所有观察信息

ios - 如何在ios中按字母顺序显示搜索结果

ios - NetworkReachabilityManager 或 Reachability 无法识别在没有互联网的情况下连接 wifi

ios - 如何 'pop' 导航 Controller 及其所有 View Controller

SwiftUI 可识别的 ForEach 最初从空开始时不会更新

android - 如何使 Modal 在 react-native 中显示为不同的组件

swift - 为什么我的 2D 图像在使用 ARKit 显示时会改变大小?

ios - UIKit 中的 Swift 组合。某些用户的 URLSession dataTaskPublisher NSURLErrorDomain -1