Обработка ошибок в Combine (Swift, iOS) - PullRequest
0 голосов
/ 22 марта 2020

Я не знаю, как бороться с ошибками в потоке комбайнов. Я хотел бы иметь возможность отлавливать ошибки из функции объединения.

Может ли кто-нибудь помочь объяснить, что я здесь делаю неправильно и как мне следует перехватывать ошибку с комбайном?

Примечание. Приведенная ниже функция является лишь примером, иллюстрирующим случай, когда ошибка могла бы быть обнаружена вместо сбоя приложения.

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, но "комбинированным способом".

Я использовал выборку данных URL в качестве примера, но это может быть с чем-то еще.

Каков рекомендуемый способ вызывать и отлавливать ошибки в потоке объединения? Например, вернуть издателя с указанной ошибкой c? Если да, то как я могу это сделать?


РЕДАКТИРОВАТЬ

Без объединения, я бы просто выдал ошибку, добавил ключевое слово 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
}

1 Ответ

1 голос
/ 23 марта 2020

Если инициализатор URL(string:) не работает (возвращает nil), вы должны решить, в какую ошибку вы хотите это исправить. Допустим, вы хотите превратить его в URLError. Итак, если URL(string:) возвращает nil, создайте URLError и используйте Fail издателя, чтобы опубликовать sh it:

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()
}

Но если вы действительно хотите добавить больше, объедините его , вы можете использовать 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($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()
}

Но вещи трудно читать. Мы можем выделить использование 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
            })
    }
}

И затем использовать его следующим образом:

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()
}

Обратите внимание, что если по пути вы совершите любую ошибку, вы, вероятно, получите непостижимое сообщение об ошибке, и вам придется разбирать свой длинный конвейер, чтобы Swift мог сказать вам, что на самом деле не так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...