Swift. Combine. Is there any way to call a publisher block more than once when retry?

自古美人都是妖i 提交于 2020-01-24 05:35:05

问题


I want to make a network request more than one time when some error occurs using retry() from Swift/Combine. The block inside the publisher is called once only which means one only one request is made for a real app when error happens. My code is:

import UIKit
import Combine
import PlaygroundSupport

enum TestFailureCondition: Error {
    case invalidServerResponse
}

var backgroundQueue: DispatchQueue = DispatchQueue(label: "backgroundQueue")

var failPublisher: AnyPublisher<(Data, URLResponse), Error> {
    Future<(Data, URLResponse), Error> { promise in
        print("Attempt to call")
        backgroundQueue.asyncAfter(deadline: .now() + Double.random(in: 1..<3)) {
            promise(.failure(TestFailureCondition.invalidServerResponse))
        }
    }
    .eraseToAnyPublisher()
}

let cancellable = failPublisher
.print("(1)>")
.retry(3)
.print("(2)>")
.sink(receiveCompletion: { fini in
    print(" ** .sink() received the completion:", String(describing: fini))


    PlaygroundPage.current.finishExecution()
}, receiveValue: { stringValue in
    print(" ** .sink() received \(stringValue)")
})

PlaygroundPage.current.needsIndefiniteExecution = true

I expect that backgroundQueue.asyncAfter(deadline) is called three time before some error happens. Does anyone know why?


回答1:


Future runs its body once, as soon as it is created, even if nothing subscribes to it. It saves the result and completes all subscriptions with that same result. So using the retry operator on a Future won't make the Future run its body again on failure.

You want each subscription to run the body again. You can do that by wrapping your Future in a Deferred. The Deferred will create a new Future for every subscription.

var failPublisher: AnyPublisher<(Data, URLResponse), Error> {
    Deferred {
        Future<(Data, URLResponse), Error> { promise in
            print("Attempt to call")
            backgroundQueue.asyncAfter(deadline: .now() + Double.random(in: 1..<3)) {
                promise(.failure(TestFailureCondition.invalidServerResponse))
            }
        }
    }
    .eraseToAnyPublisher()
}



回答2:


I managed to achieve the expected behaviour by utilising tryCatch() function and making another request: Link

The link contains two ways to achieve the same behaviour including Deferred {} mentioned above.



来源:https://stackoverflow.com/questions/59224781/swift-combine-is-there-any-way-to-call-a-publisher-block-more-than-once-when-r

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!