Dennis Calla
Dennis Calla

Reputation: 889

Swift Combine - How to throw an error and stop execution

I have an HTTP request publisher that when a 401 error is returned, I want to stop execution and display my sign in screen.

Here's part of my code:

cancellable = fetcher.hello(helloRequest: HelloRequest(name: self.name))
            .print("fetcher.hello")
            .catch { _ in
                // TODO: how to handle errors with request?
                Just(HelloResponse.placeHolder)
            }
            .flatMap { response -> AnyPublisher<HelloResponse, Never> in
                if response.imageUrl == nil || response.imageUrl == "" {
                    // If there's no image to download just return the response
                    return Just(response).eraseToAnyPublisher()
                }
                else {
                    // Chain together request and download image
                    return fetcher.downloadImage(url: response.imageUrl!)
                        .print("fetcher.hello.downloadImage")
                        .catch { _ in
                            // If there was an error downloading the image, replace it with a placeholder
                            Just(UIImage(named: "placeholder_square")!)
                        }
                        .map {
                            // Add image to response
                            HelloResponse(message: response.message, visitCount: response.visitCount, imageUrl: response.imageUrl, image: $0)
                        }
                        .eraseToAnyPublisher()
                }
            }
            .sink(receiveCompletion: { _ in }, receiveValue: { self.response = $0.self })

So part of the problem is the following flatMap that will download an image if necessary. The output type is AnyPublisher<HelloResponse, Never> (I couldn't think of another way to do that). Right now the catch returns a placeholder model and worked fine. But now I've swallowed the error. I thought maybe Empty() publisher would work but it didn't seem right. I tried Fail() but apparently catch is a Never (makes sense). Thanks!

Upvotes: 3

Views: 9677

Answers (1)

Dennis Calla
Dennis Calla

Reputation: 889

Using New Dev's suggestion about setFailureType I now have it working. When I originally had the flatMap returning AnyPublisher<HelloWorld, Error> it would not compile because Just is a Never. So I changed the flatMap to Never fail but then I couldn't remove the catch because the previous failure type was Error. Adding setFailureType to the Justs in the flatMap now let me change the flatMap to return Error and I removed the catch.

cancellable = fetcher.hello(helloRequest: HelloRequest(name: self.name))
            .print("fetcher.hello")
            .flatMap { response -> AnyPublisher<HelloResponse, Error> in
                if response.imageUrl == nil || response.imageUrl == "" {
                    // If there's no image to download just return the response
                    return Just(response)
                        .setFailureType(to: Error.self) // This allows us to set a failure type (Just is Never) so that it will match AnyPublisher<HelloResponse, Error>
                        .eraseToAnyPublisher()
                }
                else {
                    // Chain together request and download image
                    return fetcher.downloadImage(url: response.imageUrl!)
                        .print("fetcher.hello.downloadImage")
                        .catch { _ in
                            // If there was an error downloading the image, replace it with a placeholder
                            Just(UIImage(named: "placeholder_square")!)
                                .setFailureType(to: Error.self) // This allows us to set a failure type (Just is Never) so that it will match AnyPublisher<HelloResponse, Error>
                        }
                        .map {
                            // Add image to response
                            HelloResponse(message: response.message, visitCount: response.visitCount, imageUrl: response.imageUrl, image: $0)
                        }
                        .eraseToAnyPublisher()
                }
            }
            .sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    print("Request error: \(String(describing: error))")
                }
            }, receiveValue: {
                self.response = $0.self
            })
    }

Upvotes: 4

Related Questions