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

Xys*_*Xys 0 ios swift combine

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

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

注意:下面的功能仅仅是一个例子来说明,其中错误的情况下可能会被抓,而不是崩溃的应用程序。

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
}
Run Code Online (Sandbox Code Playgroud)

正如评论中所解释的,我希望能够以dataFromURL“组合方式”捕获函数之外的任何错误。

我以获取 URL 数据为例,但它也可以用于其他任何事情。

使用组合流引发和捕获错误的推荐方法是什么?例如,是否返回具有特定错误的发布者?如果是这样,我该怎么做?


编辑

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

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

guard <url is valid> else {
    return PublisherError(URLError.urlNotValid)
}
Run Code Online (Sandbox Code Playgroud)

并且可能会像这样被抓住:

dataFromURL
.print()
.onError { error in
   // handle error here
}
.sink { result in
    // no error
}
Run Code Online (Sandbox Code Playgroud)

rob*_*off 11

如果URL(string:)初始化程序失败(返回nil),您必须决定要将其变成什么错误。假设你想把它变成一个URLError. 因此,如果URL(string:)返回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()
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您真的想将更多的 Combine 铲到其中,则可以使用 aResult.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($0) } // This is Optional.map
            ?? Result.failure(URLError(.badURL, userInfo: [NSURLErrorKey: urlString]))
        )
        .publisher
        .flatMap({
            URLSession.shared
                .dataTaskPublisher(for: $0)
                .tryMap { result -> T in
                    return try decoder.decode(T.self, from: result.data)
            }
        })
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)

但事情变得难以阅读。我们可以将使用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 ({
                $0
                    .map { Result.success($0).publisher }
                    ?? Result.failure(error).publisher
            })
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

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: $0) }
        .unwrapOrFail(with: URLError(.badURL, userInfo: [NSURLErrorKey: urlString]))
        .flatMap({
            URLSession.shared
                .dataTaskPublisher(for: $0)
                .tryMap { result -> T in
                    return try decoder.decode(T.self, from: result.data)
            }
        })
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)

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