Issue #451

For some services, we need to deal with separated APIs for getting ids and getting detail based on id.

To chain requests, we can use flatMap and Sequence, then collect to wait and get all elements in a single publish

FlatMap

Transforms all elements from an upstream publisher into a new or existing publisher.

struct FlatMap<NewPublisher, Upstream> where NewPublisher : Publisher, Upstream : Publisher, NewPublisher.Failure == Upstream.Failure

Publishers.Sequence

A publisher that publishes a given sequence of elements.

struct Sequence<Elements, Failure> where Elements : Sequence, Failure : Error
func fetchItems(completion: @escaping ([ItemProtocol]) -> Void) {
    requestCancellable = URLSession.shared
        .dataTaskPublisher(for: topStoriesUrl())
        .map({ $0.data })
        .decode(type: [Int].self, decoder: JSONDecoder())
        .flatMap({ (ids: [Int]) -> AnyPublisher<[HackerNews.Item], Error> in
            let publishers = ids.prefix(10).map({ id in
                return URLSession.shared
                    .dataTaskPublisher(for: self.storyUrl(id: id))
                    .map({ $0.data })
                    .decode(type: HackerNews.Item.self, decoder: JSONDecoder())
                    .eraseToAnyPublisher()
            })

            return Publishers.Sequence<[AnyPublisher<HackerNews.Item, Error>], Error>(sequence: publishers)
                // Publishers.Sequence<[AnyPublisher<HackerNews.Item, Error>], Error>
                .flatMap({ $0 })
                // Publishers.FlatMap<AnyPublisher<HackerNews.Item, Error>, Publishers.Sequence<[AnyPublisher<HackerNews.Item, Error>], Error>>
                .collect()
                // Publishers.Collect<Publishers.FlatMap<AnyPublisher<HackerNews.Item, Error>, Publishers.Sequence<[AnyPublisher<HackerNews.Item, Error>], Error>>>
                .eraseToAnyPublisher()
                // AnyPublisher<[HackerNews.Item], Error>
        })
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
        .sink(receiveCompletion: { completionStatus in
            switch completionStatus {
            case .finished:
                break
            case .failure(let error):
                print(error)
            }
        }, receiveValue: { items in
            completion(items)
        })
}