Compte tenu du code suivant:
we're in the else case we're in the else case replaced Error we're in the else case ...
Chaque fois que j'appuie sur le bouton, j'obtiens que we're in the else case
Bool.random()
jusqu'à Bool.random()
que Bool.random()
soit vrai - maintenant une erreur est générée. J'ai essayé différentes choses, mais je n'ai pas réussi à attraper / remplacer / ignorer l'erreur et continuer après avoir appuyé sur le bouton.
Dans l'exemple de code, j'aimerais avoir par exemple la sortie suivante
enum MyError: Error { case someError } myButton.publisher(for: .touchUpInside).tryMap({ _ in if Bool.random() { throw MyError.someError } else { return "we're in the else case" } }) .replaceError(with: "replaced Error") .sink(receiveCompletion: { (completed) in print(completed) }, receiveValue: { (sadf) in print(sadf) }).store(in: &cancellables)
au lieu de cela, je finished
après l' replaced error
et aucun événement n'est émis.
Modifier Étant donné un éditeur avec AnyPublisher<String, Error>
, comment puis-je le transformer en AnyPublisher<String, Never>
sans terminer lorsqu'une erreur se produit, c'est-à-dire ignorer les erreurs émises par l'éditeur d'origine?
7 Réponses :
Insérez simplement flatMap comme suit et vous pouvez réaliser ce que vous voulez
let firstPublisher = {(value: Int) -> AnyPublisher<String, Error> in Just(value).tryMap({ _ -> String in if Bool.random() { throw MyError.someError } else { return "we're in the else case" }}).eraseToAnyPublisher() } Just(1).flatMap{ (value: Int) in return firstPublisher(value).replaceError(with: "replaced Error") }.sink(receiveCompletion: { (completed) in print(completed) }, receiveValue: { (sadf) in print(sadf) }).store(in: &cancellables)
Le modèle de travail ressemble à ceci:
Just(parameter). flatMap{ (value)->AnyPublisher<String, Never> in return MyPublisher(value).catch { <String, Never>() } }.sink(....)
Si nous utilisons l'exemple ci-dessus, cela pourrait être comme ceci:
self.myButton.publisher(for: \.touchUpInside).flatMap{ (data: Bool) in return Just(data).tryMap({ _ -> String in if Bool.random() { throw MyError.someError } else { return "we're in the else case" }}).replaceError(with: "replaced Error") }.sink(receiveCompletion: { (completed) in print(completed) }, receiveValue: { (sadf) in print(sadf) }).store(in: &cancellables)
Ici, vous pouvez remplacer le firstPublisher
par AnyPublisher qui prend un paramètre.
Ici aussi, le firstPublisher n'a qu'une seule valeur, il ne peut produire qu'une seule valeur. Mais si votre éditeur peut produire plusieurs valeurs, il ne se terminera pas avant que toutes les valeurs aient été émises.
ce n'est pas ce que je veux ... Je voudrais "effacer" l'erreur AVANT l'évier et APRÈS le tryMap. TryMap n'était utilisé que pour simuler un éditeur générant une erreur.
J'utilise également tryMap pour simuler l'erreur. Vous venez de mettre toute la logique try / catch ou try / replace ou catch dans le bloc flatmap, cela ne mettra pas fin à l'éditeur même après l'erreur de remplacement. Je pense que c'est ce dont vous avez besoin. Vous pouvez remplacer tryMap par n'importe quel autre.
mais vous avez l'erreur de lancer l'éditeur dans le flatMap
, ce n'est pas ce que je recherche. Je cherche quelque chose où let something: AnyPublisher<String, Error> = ...
peut être transformé en AnyPublisher<String, Never>
- ou est-ce que je manque encore quelque chose?
FlatMap est un tel opérateur. Il empêche l'éditeur de résilier. ReplaceError peut également fonctionner, mais une fois qu'il corrige l'erreur, il mettra fin à l'éditeur. Vous avez donc besoin de cet opérateur intégré dans FlatMap. Ainsi, une fois l'erreur corrigée, l'éditeur d'origine sera repris. Vous pouvez exécuter le code pour voir si c'est le même que vous avez mentionné.
et si je n'ai PAS accès à l'éditeur d'origine (disons que c'est déjà un AnyPublisher<String, Error>
), comment puis-je l'envelopper dans flatMap?
Je vois. Vous voulez dire que vous n'avez pas d'éditeur d'UIButton?
Vous pouvez mettre votre éditeur dans FlatMap.
et quel est l'éditeur sur lequel flatMap s'appelle? L'avez-vous essayé?
Tout éditeur va bien avant FlatMap et insérez votre éditeur d'erreur dans FlatMap
ce n'est absolument pas ce que je veux. Je veux que cet éditeur se transforme, rien d'autre.
Actuellement, si vous utilisez votre code, une fois l'erreur remplacée, l'éditeur sera résilié et l'impression sera terminée. Eh bien, je crée moi-même un éditeur UIButton avec la propriété 'touchupinsder'. Puis accrochez-le à FlatMap. Il continue de fonctionner même s'il a l'erreur, il ne s'imprime jamais terminé. Le FlatMap est donc l'opérateur dont vous avez besoin ici.
Je suggère d'utiliser Publisher
avec typealias Failure = Never
et la sortie en tant que résultat facultatif: typealias Output = Result<YourSuccessType, YourFailtureType>
Pour ce faire, vous pouvez utiliser l'opérateur catch
et l'éditeur Empty
:
let stringErrorPublisher = Just("Hello") .setFailureType(to: Error.self) .eraseToAnyPublisher() // AnyPublisher<String, Error> let stringPublisher = stringErrorPublisher .catch { _ in Empty<String, Never>() } .eraseToAnyPublisher() // AnyPublisher<String, Never>
ici, je suis confronté au même problème. Dès que l'éditeur initial renvoie une erreur, la complétion est appelée et aucune valeur supplémentaire n'est émise.
Il y a une surcharge sur l'initialiseur pour Empty<String, Never>(completeImmediately: false) }
qui l'empêche de se terminer, cela aiderait-il?
malheureusement, ce n'est pas le cas. Le bloc de complétion n'est pas appelé, mais aucune valeur n'est émise non plus.
La lecture de la documentation pour catch
indique qu'elle remplace l'éditeur en amont, ce qui explique pourquoi plus aucune valeur n'est envoyée. Êtes-vous en mesure de renvoyer une nouvelle version de votre éditeur d'origine dans le catch
plutôt qu'un Empty<String, Never>
? par exemple: .catch { _ in myButton.publisher(for: .touchUpInside)... }
Je me débattais aussi avec ce problème et j'ai finalement trouvé une solution. Une chose à comprendre est que vous ne pouvez pas récupérer d'un flux terminé. La solution consiste à renvoyer un Result
au lieu d'une Error
.
let subscriber = PassthroughSubject<Result<String, MyError>, Never>() subscriber .sink(receiveValue: { result in switch result { case .success(let value): print("Received value: \(value)") case .failure(let error): print("Failure: \(String(describing: error))") } })
Pour toute autre personne lisant ce fil et essayant de compiler le code, vous devrez importer le code de cet article (pour la partie button.publisher(for: .touchUpIsinde)
).
Bonus, voici le code pour gérer les erreurs avec un PassthroughSubject
et ne jamais terminer le flux:
let button = UIButton() button.publisher(for: .touchUpInside) .map({ control -> Result<String, Error> in if Bool.random() { return .failure(MyError.someError) } else { return .success("we're in the else case") } }).sink (receiveValue: { (result) in switch(result) { case .success(let value): print("Received value: \(value)") case .failure(let error): print("Failure: \(String(describing: error))") } })
Vous ne pouvez pas utiliser PassthroughSubject<String, MyError>()
directement, sinon, le flux se terminera lorsqu'une erreur se produit.
Je crois que la réponse d'E. Coms est correcte, mais je vais le dire beaucoup plus simple. La clé pour gérer les erreurs sans que le pipeline arrête le traitement des valeurs après une erreur est d'imbriquer votre éditeur de gestion des erreurs dans flatMap
:
1 666 3 finished
Production:
import UIKit import Combine enum MyError: Error { case someError } let cancel = [1,2,3] .publisher .flatMap { value in Just(value) .tryMap { value throws -> Int in if value == 2 { throw MyError.someError } return value } .replaceError(with: 666) } .sink(receiveCompletion: { (completed) in print(completed) }, receiveValue: { (sadf) in print(sadf) })
Vous pouvez exécuter cet exemple dans une aire de jeux.
Concernant la modification du PO:
Modifier Étant donné un éditeur avec
AnyPublisher<String, Error>
, comment puis-je le transformer enAnyPublisher<String, Never>
sans terminer lorsqu'une erreur se produit, c'est-à-dire ignorer les erreurs émises par l'éditeur d'origine?
Vous ne pouvez pas.
Il y avait un film de la WWDC mentionné, et je crois que c'est "Combine in Practice" à partir de 2019, commencez à regarder vers 6h24: https://developer.apple.com/wwdc19/721
Oui, .catch()
met fin à l'éditeur en amont (film 7:45) et le remplace par un .catch
donné dans les arguments de .catch
ce qui entraîne généralement la .finished
de .finished
lors de l'utilisation de Just()
comme éditeur de remplacement.
Si l'éditeur d'origine doit continuer à fonctionner après un échec, une construction impliquant .flatMap()
est requise (film 9:34). L'opérateur entraînant un éventuel échec doit être exécuté dans le .flatMap
, et peut y être traité si nécessaire. L'astuce est d'utiliser
PlaygroundSupport.PlaygroundPage.current.needsIndefiniteExecution = true enum MyError: Error { case someError } let cancellable = Timer.publish(every: 1, on: .main, in: .default) .autoconnect() .flatMap({ (input) in Just(input) .tryMap({ (input) -> String in if Bool.random() { throw MyError.someError } else { return "we're in the else case" } }) .catch { (error) in Just("replaced error") } }) .sink(receiveCompletion: { (completion) in print(completion) PlaygroundSupport.PlaygroundPage.current.finishExecution() }) { (output) in print(output) }
au lieu de
.catch { return Just(replacement) } // DOES STOP UPSTREAM PUBLISHER
À l'intérieur de .flatMap
vous remplacez toujours l'éditeur et ne vous souciez donc pas de savoir si cet éditeur de remplacement est résilié par .catch
, car c'est déjà un remplaçant et notre éditeur d'origine en amont est en sécurité. Cet exemple est tiré du film.
C'est aussi la réponse à votre question Edit .flatMap
sur la façon de transformer un <Output, Error>
en <Output, Never>
, puisque le .flatMap
ne génère aucune erreur, c'est Jamais avant et après le flatMap. Toutes les étapes liées aux erreurs sont encapsulées dans le flatMap. (Astuce pour vérifier l' Failure=Never
: si vous obtenez l' .assign(to:)
Xcode pour .assign(to:)
alors je crois que vous avez un échec = Jamais stream, cet abonné n'est pas disponible autrement. Et enfin le code complet du terrain
.flatMap { data in return Just(data).decode(...).catch { Just(replacement) } }
L'éditeur émet jusqu'à ce qu'il se termine ou échoue (avec une erreur), après quoi le flux sera arrêté.
Une façon de surmonter ce problème consiste à utiliser un résultat comme type d'éditeur
Éditeur
service.value .sink { [weak self] in switch $0 { case let .success(value): self?.receiveServiceValue(value) case let .failure(error): self?.receiveServiceError(error) } } .store(in: &subscriptions)
Abonné
protocol SerivceProtocol { var value: Published<Result<Double, MyError>>.Publisher { get } }
Vous devez utiliser catch {}
mais quoi écrire dans le bloc Catch? Si j'utilise un Just, l'éditeur termine aussi wel
C'est une bonne question, ce que vous attendez est un nouvel éditeur qui est le même que celui actuel. Dans un cas courant, peut-être que «puits» n'est pas un abonné idéal ici. Essayez un sujet avant de couler
vous voulez dire un sujet personnalisé, c'est-à-dire un sujet qui "transmet" uniquement des valeurs et non des erreurs?
J'ai trouvé la réponse maintenant, utilisez simplement FlatMap, regardez les vidéos de la WWDC
avez-vous un lien vers le calendrier exact ou un petit exemple?
@swalkner comment avez-vous défini myButton?
c'est du storyboard:
@IBOutlet var myButton: UIButton!
@swalkner J'ai publié ma réponse
Je vois ton problème maintenant. Vous devez diviser votre éditeur initial en deux parties. L'un est sur flatMap, sur flatMap d'initié. Par exemple, si vous pouvez produire un éditeur avec un paramètre, vous pouvez faire quelque chose comme ceci: Just (paramètre) .flatMap {(value) -> AnyPublisher <String, Never> in return MyPublisher (value) .catch {<String, Jamais> ()}} .sink (....) // MyPublisher (valeur) vous donnera un AnyPublisher <String, Error>. Catch peut le convertir en AnyPublisher <String, Never>. FlatMap continuera à produire de nouveaux éditeurs et ne finira jamais
@ E.Coms Je ne comprends pas, pouvez-vous mettre à jour votre réponse pour qu'elle soit formatée? 🙂
J'ai ajouté une nouvelle réponse comprenant également un lien vers le film WWDC'19 mentionné ci-dessus. N'hésitez pas à l'accepter comme la bonne réponse si vous pensez que cela vous aide.
Vous pouvez renvoyer votre propre erreur dans catch block: catch (let error) {return Fail (error: error) .receive (on: DispatchQueue.main) .eraseToAnyPublisher ()}