skip to Main Content

I’m trying to make Zip two Publishers with two different functions, but it’s not working as expected. I have a chance to one may failure and one success. but even one is successful I’m not getting a successful response in the sink. Here is my code, help is greatly appreciated.


struct ContentView: View {
    @State var buttonTapped = false
    @State var cancellable = Set<AnyCancellable>()

    var body: some View {
        Group {
           Text("Home")
        }.onAppear {
            Publishers.Zip(fetchData1(), fetchData2())
                .sink(receiveCompletion: { first in
                switch first {
                case .failure(let er):
                    print(er)
                case .finished:
                    print("ss")
                }
            }, receiveValue: { (a, b) in
               print(a, b)
             // Not printing success value
            }).store(in: &cancellable)
        }
    }

    func fetchData1() -> Future<Bool, ErrorType> {
        return Future { promise in
            promise(.failure(.error("fetchData1 failed")))
        }
    }
    
    func fetchData2() -> Future<String, ErrorType> {
        return Future { promise in
            promise(.success("fetchData success "))
        }
    }
}

enum ErrorType: Error {
    case error(String)
}


2

Answers


  1. Below is a playground that demonstrates each case.

    It includes a function makeRequest that creates a Future to simulate a request that either succeeds or fails.

    At the heart of the sample is a pattern:

    someFailableRequest
      .tryMap { return a value indicating success or failure }
      .catch { _ in Just(a value indicating success or failure) }
    

    if the request succeeds, the value we pass to later stages indicates success. If it fails, then catch returns a publisher that generates the same kind of thing, but indicating failure.

    That pattern is used recursively to handle each case in a slightly different way.

    import Cocoa
    import Combine
    import Foundation
    
    func makeRequest(shouldFail: Bool) -> AnyPublisher<String, Error> {
        return Future<String, Error> { fulfill in
            let delay = (100...500).randomElement()!
            DispatchQueue.global(qos: .background).asyncAfter(
                deadline: .now() + DispatchTimeInterval.milliseconds(delay)) {
                    if shouldFail {
                        fulfill(.failure(NSError(domain: "sadness", code: -1)))
                    } else {
                        fulfill(.success("Yea!"))
                    }
                }
        }.eraseToAnyPublisher()
    }
    
    // Case 1.  First request fails, don't do second one
    let request1 = makeRequest(shouldFail: true)
    let request2 = makeRequest(shouldFail: false)
    
    let requestChain = request1
        .tryMap { return $0 as String? }
        .catch { _ in return Just(nil as String?) }
        .flatMap { (firstResult: String?) -> AnyPublisher<(String?, String?), Never> in
            if firstResult != nil {
                return request2
                    .tryMap { result2 in (firstResult, result2) }
                    .catch { _ in Just((firstResult, nil)) }
                    .eraseToAnyPublisher()
            } else {
                return Just((nil as String?, nil as String?)).eraseToAnyPublisher()
            }
        }
    
    let cancellable = requestChain.sink { debugPrint("first chain ($0)") }
    
    // Case2 : continue second request even if first fails.
    
    let request3 = makeRequest(shouldFail: true)
    let request4 = makeRequest(shouldFail: false)
    
    let requestChain1 =
        request3
        .tryMap { firstResult -> AnyPublisher<(String?,String?),Never> in
            request4
                .tryMap { secondResult in (firstResult, secondResult) }
                .catch { _ in Just( (firstResult as String?, nil) ).eraseToAnyPublisher() }
                .eraseToAnyPublisher()
        }
        .catch { _ in
            Just(request4
                .tryMap { secondResult in (nil as String?, secondResult) }
                .catch { _ in Just( (nil as String?, nil as String?) ) }
                .eraseToAnyPublisher())
        }
        .flatMap { $0 }
    
    
    let cancellable2 = requestChain1.sink { debugPrint("second chain ($0)") }
    
    Login or Signup to reply.
  2. Your code is good. Not sure your requirement, but it works this way,

    • either of them fail, all fail
    • only two succeed, it succeeds
    • I was guessing we can’t get one data if the other fails

    Check out the doc from apple:
    Use Publishers.Zip to combine the latest elements from two publishers and emit a tuple to the downstream. The returned publisher waits until both publishers have emitted an event, then delivers the oldest unconsumed event from each publisher together as a tuple to the subscriber.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search