Comment lancer une erreur de l'opérateur de carte RxJS (angulaire)

93

Je veux lancer une erreur de l' opérateur cartographique de mon observable en fonction d'une condition. Par exemple, si les données API correctes ne sont pas reçues. Veuillez consulter le code suivant:

private userAuthenticate( email: string, password: string ) {
    return this.httpPost(`${this.baseApiUrl}/auth?format=json&provider=login`, {userName: email, password: password})
        .map( res => { 
            if ( res.bearerToken ) {
                return this.saveJwt(res.bearerToken); 
            } else {
                // THIS DOESN'T THROW ERROR --------------------
                return Observable.throw('Valid token not returned');
            }
        })
        .catch( err => Observable.throw(this.logError(err) )
        .finally( () => console.log("Authentication done.") );
}

Fondamentalement, comme vous pouvez le voir dans le code, si la réponse (objet res) n'a pas 'bearerToken', je veux jeter une erreur. Ainsi, dans mon abonnement, il entre dans le 2ème paramètre (handleError) mentionné ci-dessous.

.subscribe(success, handleError)

Aucune suggestion?

Hassan
la source
4
Et quoi throw 'Valid token not returned';?
Günter Zöchbauer
Échec de la compilation
Hassan
Message d'erreur exact s'il vous plaît.
Günter Zöchbauer
2
Désolé, cela ne fonctionne pas avec return throw 'message here'mais fonctionne sans le returnmot clé Laissez-moi vérifier si cela fonctionne correctement logiquement.
Hassan
Le texte d'erreur n'est pas reçu dans la subscribeméthode et le .finally()dans le flux se déclenche également. (Cependant, l'exécution est arrêtée, ce qui est une bonne chose)
Hassan

Réponses:

141

Jetez simplement l'erreur dans l' map()opérateur. Tous les rappels dans RxJS sont encapsulés avec des blocs try-catch afin qu'ils soient interceptés puis envoyés en tant que errornotification.

Cela signifie que vous ne retournez rien et lancez simplement l'erreur:

map(res => { 
  if (res.bearerToken) {
    return this.saveJwt(res.bearerToken); 
  } else {
    throw new Error('Valid token not returned');
  }
})

Le throwError()(ancien Observable.throw()dans RxJS 5) est un observable qui envoie simplement une errornotification mais map()ne se soucie pas de ce que vous retournez. Même si vous renvoyez un observable, map()celui-ci sera transmis comme nextnotification.

Dernière chose, vous n'avez probablement pas besoin d'utiliser .catchError()(anciennement catch()dans RxJS 5). Si vous devez effectuer des effets secondaires lorsqu'une erreur se produit, il est préférable d'utiliser tap(null, err => console.log(err))(anciennement do()dans RxJS 5) par exemple.

Janvier 2019: mise à jour pour RxJS 6

Martin
la source
1
Merci @martin - Oui, votre solution fonctionne. J'ai également eu un problème dans ma méthode logError que @ GünterZöchbauer a souligné. J'avais à returnl'objet d'erreur et maintenant cela fonctionne parfaitement :) Merci!
Hassan
@martin: Pourriez-vous s'il vous plaît développer pourquoi nous ne voudrions pas vous .catch () ici?
Bob
1
@Bob Parce que l'OP n'utilisait catch()que pour enregistrer et renvoyer l'erreur, ce qui n'est pas nécessaire si vous voulez juste effectuer un effet secondaire (journalisation de l'erreur) et il est plus facile à utiliser justedo()
martin
1
Est-ce identique à return throwError(new Error('Valid token not returned'));?
Simon_Weaver
@Simon_Weaver non, ce n'est pas le cas. return throwError()retourne an Observable<never>, cela interrompt immédiatement le flux observable, sans aucun retour.
Réintégrer Monica
25

Si vous sentez que cela throw new Error()semble inobservable, vous pouvez utiliser throwError(...)avec switchMapau lieu de map(la différence étant switchMaprenvoie une nouvelle observable):

// this is the import needed for throwError()
import { throwError } from 'rxjs';


// RxJS 6+ syntax
this.httpPost.pipe(switchMap(res => { 
   if (res.bearerToken) {
      return of(this.saveJwt(res.bearerToken)); 
   } 
   else {
      return throwError('Valid token not returned');  // this is 
   }
});

ou plus brièvement:

this.httpPost.pipe(switchMap(res => (res.bearerToken) ? 
                                    of(this.saveJwt(res.bearerToken)) : 
                                    throwError('Valid token not returned')
));

Le comportement sera le même, c'est juste une syntaxe différente.

Vous dites littéralement «basculer» de l'observable http dans le tube vers une autre observable, qui consiste soit simplement à «encapsuler» la valeur de sortie, soit à une nouvelle observable «erreur».

N'oubliez pas de mettre ofou vous obtiendrez des messages d'erreur déroutants.

La beauté de «switchMap» est également que vous pouvez renvoyer une toute nouvelle «chaîne» de commandes si vous le souhaitez - quelle que soit la logique dont vous avez besoin saveJwt.

Simon_Weaver
la source
4
Une fois que j'ai commencé à penser switchMapà une ifdéclaration asynchrone - les choses avaient beaucoup plus de sens :-)
Simon_Weaver
3

Même si cette question est déjà répondue, j'aimerais partager ma propre approche (même si elle n'est que légèrement différente de celle ci-dessus).

Je déciderais de ce qui est renvoyé séparément du mappage et vice versa. Je ne sais pas quel opérateur est le meilleur pour cela, je vais donc l'utiliser tap.

this.httpPost.pipe(
  tap(res => { 
    if (!res.bearerToken) {
      throw new Error('Valid token not returned');
    }
  }),
  map(res => this.saveJwt(res.bearerToken)),
);
christo8989
la source
la valeur de retour de tapest ignorée. ce code fait une chose différente de ce qu'il dit
sf
Je m'habitue toujours aux rxjs. L'utilisation de switchMap serait-elle meilleure? Quelqu'un peut-il suggérer un opérateur différent ou modifier directement?
christo8989
Je pense que cette suggestion throw new Error()est la meilleure option jusqu'à présent
sf