Home > Software design >  An Ordered chain of Publishers
An Ordered chain of Publishers

Time:05-12

I have two Publishers, I want to feed the second one with the result of first one, I could do what I wanted by calling the second one nested into the first one, it works but it does not feel good to look at, is there a better way to do it?

the first Publisher returns AnyPublisher<URL, Error> and the second one returns AnyPublisher<JsonModel, Error>

func upload(input: URL, output: URL) {
    let converter = MP3Converter()
    converter.convert(input: input, output: output)
        .sink { [weak self] result in
            if case .failure(let error) = result {
                ConsoleLogger.log(error)
                self?.uploadedRecordingURL = nil
            }
        } receiveValue: { url in
            guard let data = try? Data(contentsOf: url) else {
                return
            }
            self.repository.uploadCover(data: data)
                .sink(receiveCompletion: { [weak self] result in
                    if case .failure(let error) = result {
                        ConsoleLogger.log(error)
                        self?.uploadedRecordingURL = nil
                    }
            }, receiveValue: { [weak self] response in
                self?.uploadedRecordingURL = response.fileURL
            
            }).store(in: &self.disposables)
        }.store(in: &disposables)
}

CodePudding user response:

I think you've already deduced this, but just do be clear: you are using sink and its receiveValue completely wrong. Don't start a new chain or do any significant work here at all! There should be a simple sink at the end, followed by store to anchor the chain, and that's the end.

You are looking for flatMap. That is how you chain publishers. (See my https://www.apeth.com/UnderstandingCombine/operators/operatorsTransformersBlockers/operatorsflatmap.html.) You may have to give some thought to exactly what needs to pass from the first publisher and its chain down into the flatMap closure and what needs to pass on down the chain from there.

CodePudding user response:

You could use flatmap to chain your publishers together.

// the code would roughly look like this
converter.convert(input: input, output: output).flatMap { data in
    return self.repository.uploadCover(data: data)
}.sink(receiveCompletion: { [weak self] result in
    if case .failure(let error) = result {
        ConsoleLogger.log(error)
        self?.uploadedRecordingURL = nil
    }
}, receiveValue: { [weak self] response in
    self?.uploadedRecordingURL = response.fileURL
}).store(in: &self.disposables)

Here's an example in playgrounds that compiles

import Combine
import Foundation

enum SomeError: Error {
}

let subject0 = CurrentValueSubject<Data, SomeError>(Data())
let pub0 = subject0.eraseToAnyPublisher()

func repoUpload(data: Data) -> AnyPublisher<URL, SomeError> {
    // do the real work, this is just to get it to compile
    let subject1 = CurrentValueSubject<URL, SomeError>(URL(fileURLWithPath: "Somepath"))
    return subject1.eraseToAnyPublisher()
}

var disposables = Set<AnyCancellable>()
pub0.flatMap { data in
    return repoUpload(data: data)
}.sink(receiveCompletion: { result in
 print(result)
}, receiveValue: { response in
    print(response)
}).store(in: &disposables)

  • Related