Combine map with switchToLatest confuse

I have a source publisher, and have an another publisher rely source publisher.
This is playground test code for my situation:

import Foundation
import Combine
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

typealias Image = Int

enum NetError: Error {
    case invalidImage
}

func convertImageToVideo(_ image: Image) -> AnyPublisher<Image, NetError> {
    Future { promise in
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            if image == 20 {
                promise(.failure(.invalidImage))
            } else {
                promise(.success(image))
            }
        }
    }
    .eraseToAnyPublisher()
}

var image = PassthroughSubject<Image, NetError>()

let subscription = image
    .map { image in
        convertImageToVideo(image)
    }
    .switchToLatest()
    .sink { completion in
        if case let .failure(error) = completion {
            print("Receive error: \(error)")
        }
    } receiveValue: { video in
        print("Receive new video: \(video)")
    }

image.send(0)
image.send(20)
image.send(40)

DispatchQueue.main.async {
    image.send(20)
}

DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
    print("Send 50 into image.")
    image.send(50)
}

But I only receive one error in console:

Receive error: invalidImage

This is not ideal, I want continue receive value even if convertImageToVideo method occur an error.
So I change code:

import Foundation
import Combine
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

typealias Image = Int

enum NetError: Error {
    case invalidImage
}

func convertImageToVideo(_ image: Image) -> AnyPublisher<Image, NetError> {
    Future { promise in
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            if image == 20 {
                promise(.failure(.invalidImage))
            } else {
                promise(.success(image))
            }
        }
    }
    .eraseToAnyPublisher()
}

var image = PassthroughSubject<Image, NetError>()

let subscription = image
    .map { image -> AnyPublisher<Result<Image, NetError>, Never> in
        convertImageToVideo(image)
            .map { video in
                .success(video)
            }
            .catch({ error in
                Just(.failure(error))
            })
            .eraseToAnyPublisher()
    }
    .switchToLatest()
    .sink { completion in
        if case let .failure(error) = completion {
            print("Receive error: \(error)")
        }
    } receiveValue: { video in
        print("Receive new video: \(video)")
    }

image.send(0)
image.send(20)
image.send(40)

DispatchQueue.main.async {
    image.send(20)
}

DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
    print("Send 50 into image.")
    image.send(50)
}

This time output is ideal:

Receive new video: failure(__lldb_expr_64.NetError.invalidImage)
Send 50 into image.
Receive new video: success(50)

But the error not come from completion closure, instead of it come from new value closure, I must handle error from complete closure and new value closure.
Anyone has good idea? Thanks!

Terms of Service

Privacy Policy

Cookie Policy