处理 Combine 中的错误 (Swift, iOS)

Handling errors in Combine (Swift, iOS)

我不知道如何处理合并流程中的错误。我希望能够从 Combine 函数中捕获错误。

任何人都可以帮助解释我在这里做错了什么以及我应该如何处理 捕获 Combine 的错误?

Note: The function below is just an example to illustrate a case where an error could be caught instead of crashing the app.

func dataFromURL<T: Decodable>(_ url: String, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<T, Error> {
    // 1) Example: If the URL is not well-formatted, I would like to /create/raise/return an error (in a Combine way)

    // 2) Instead of the forced unwrapping here, I would also prefer to raise a catchable error if the creation of the request fails
    let request = URLRequest(url: URL(string:url)!)

    // 3) Any kind of example dealing with potential errors, etc

    return urlSession
        .dataTaskPublisher(for: request)
        .tryMap { result -> T in
            return try decoder.decode(T.self, from: result.data)
        }
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
} 

// function in another file:
func result() {
     // I would like to be able to catch or handle errors in this function

     dataFromURL("test").print()   

    // Example : if error 1), else if error 2) etc
}

如评论中所述,我希望能够捕获 dataFromURL 函数之外但在 "Combine way" 函数中的任何错误。

我以 URL 数据获取为例,但它可以与其他任何东西一起使用。

使用 Combine 流程引发和捕获错误的推荐方法是什么?例如,是 return 有特定错误的发布者吗?如果可以,我该怎么做?


编辑

如果没有 Combine,我只会抛出一个错误,将 throws 关键字添加到函数中,然后在 result 函数中捕获错误。

但我希望 Combine 有更简单或更优雅的方式来实现这一点。例如,也许可以随时抛出的东西:

guard <url is valid> else {
    return PublisherError(URLError.urlNotValid)
}

并且可能被这样抓到:

dataFromURL
.print()
.onError { error in
   // handle error here
}
.sink { result in
    // no error
}

如果 URL(string:) 初始化器失败(返回 nil),你必须决定你想把它变成什么错误。假设您想将其变成 URLError。因此,如果 URL(string:) returns nil,创建 URLError 并使用 Fail 发布者发布它:

func jsonContents<T: Decodable>(
    ofUrl urlString: String,
    as type: T.Type,
    decodedBy decoder: JSONDecoder = JSONDecoder()
) -> AnyPublisher<T, Error> {
    guard let url = URL(string: urlString) else {
        let error = URLError(.badURL, userInfo: [NSURLErrorKey: urlString])
        return Fail(error: error).eraseToAnyPublisher()
    }

    return URLSession.shared
        .dataTaskPublisher(for: url)
        .tryMap { result -> T in
            return try decoder.decode(T.self, from: result.data)
    }
    .receive(on: DispatchQueue.main)
    .eraseToAnyPublisher()
}

但是如果你真的想把更多的 Combine 铲进去,你可以使用 Result.Publisher 而不是 Fail:

func jsonContents<T: Decodable>(
    ofUrl urlString: String,
    as type: T.Type,
    decodedBy decoder: JSONDecoder = JSONDecoder()
) -> AnyPublisher<T, Error> {
    return (
        URL(string: urlString)
            .map { Result.success([=11=]) } // This is Optional.map
            ?? Result.failure(URLError(.badURL, userInfo: [NSURLErrorKey: urlString]))
        )
        .publisher
        .flatMap({
            URLSession.shared
                .dataTaskPublisher(for: [=11=])
                .tryMap { result -> T in
                    return try decoder.decode(T.self, from: result.data)
            }
        })
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}

但是事情变得难以阅读。我们可以将 Result 的使用分解为一个新的运算符,unwrapOrFail(with:):

extension Publisher {
    func unwrapOrFail<Wrapped>(with error: Failure) -> Publishers.FlatMap<Result<Wrapped, Self.Failure>.Publisher, Self> where Output == Wrapped? {
        return self
            .flatMap ({
                [=12=]
                    .map { Result.success([=12=]).publisher }
                    ?? Result.failure(error).publisher
            })
    }
}

然后像这样使用它:

func jsonContents<T: Decodable>(
    ofUrl urlString: String,
    as type: T.Type,
    decodedBy decoder: JSONDecoder = JSONDecoder()
) -> AnyPublisher<T, Error> {
    return Result.success(urlString).publisher
        .map { URL(string: [=13=]) }
        .unwrapOrFail(with: URLError(.badURL, userInfo: [NSURLErrorKey: urlString]))
        .flatMap({
            URLSession.shared
                .dataTaskPublisher(for: [=13=])
                .tryMap { result -> T in
                    return try decoder.decode(T.self, from: result.data)
            }
        })
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}

但是请注意,如果您在此过程中犯了任何错误,您可能会收到难以理解的错误消息,并且必须拆开您的长管道才能让 Swift 告诉您真正的问题所在。