Сохранение типа сбоя с помощью комбайна tryMap - PullRequest
0 голосов
/ 04 апреля 2020

Я использую Combine, чтобы написать простой веб-скребок. Я пытаюсь отобразить возвращенные данные в строку HTML, выбрасывая ScraperError s в каждой возможной точке отказа. В конце я хочу передать эту строку моему htmlSubject, который является PassthroughSubject<String, ScraperError>, для дальнейшей обработки.

    urlSubscription = URLSession.shared
            .dataTaskPublisher(for: url)
            .mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
                ScraperError.unreachableSite
            }
            .tryMap { (data, response) -> String in
                guard let html = String(data: data, encoding: .utf8) else {
                    throw ScraperError.readFailed
                }

                return html
            }
            .subscribe(htmlSubject) // <-- Not allowed because failure type is now Error

Однако я обнаружил, что .tryMap стирает мой * От 1008 * до обычного Error, не позволяя мне связать мои htmlSubject до конца:

Метод экземпляра «подписка» требует, чтобы типы «Ошибка» и «ScraperError» были эквивалентны.

Есть ли очевидный способ обойти это, что я упускаю, или меня сбивают с толку концептуально? Я думаю об этой цепочке как о строительных блоках в большой функции, которая отображает <(Data, URLResponse), URLError> на <String, ScraperError>.

Любая помощь приветствуется.

1 Ответ

1 голос
/ 04 апреля 2020

Используйте mapError для преобразования обратно в ScraperError после tryMap:

urlSubscription = URLSession.shared
    .dataTaskPublisher(for: url)
    .mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
        ScraperError.unreachableSite
    }
    .tryMap { (data, response) -> String in
        guard let html = String(data: data, encoding: .utf8) else {
            throw ScraperError.readFailed
        }

        return html
    }
    .mapError { $0 as! ScraperError }
    .subscribe(htmlSubject)

Если вы не хотите использовать as!, вам придется выбрать другой случай для сопоставления:

    .mapError { $0 as? ScraperError ?? ScraperError.unknown }

Если вам это тоже не нравится, вы можете использовать flatMap over Result<String, ScraperError>.Publisher:

urlSubscription = URLSession.shared
    .dataTaskPublisher(for: url)
    .mapError { _ -> ScraperError in // Explicitly stating my failure type is ScraperError
        ScraperError.unreachableSite
}
.flatMap { (data, response) -> Result<String, ScraperError>.Publisher in
    guard let html = String(data: data, encoding: .utf8) else {
        return .init(.readFailed)
    }
    return .init(html)
}
.subscribe(htmlSubject)
...