Apollo-link: Le champ de réponse n'est pas défini pour networkError [apollo-link-error]

Créé le 6 nov. 2018  ·  29Commentaires  ·  Source: apollographql/apollo-link


Il semble y avoir un bogue dans la fonction onError ou dans sa documentation. Le problème a déjà été mentionné dans le n° 698 mais j'ai pensé que je le détaillerais un peu dans l'espoir d'une résolution.

Lors de l'interception d'une erreur en utilisant onError depuis apollo-boost ou directement depuis apollo-link-error , la documentation indique que l'objet d'erreur contient un objet response , qui peut être utilisé pour ignorer les erreurs.

J'ai coché la case docs car cela pourrait être un comportement intentionnel, mais comme les docs mentionnent l'objet response dans un sous-paragraphe de la section networkError , il devrait probablement être clarifié un peu si c'est l'affaire.

Comportement prévisible
La définition response.errors = null; devrait empêcher l'erreur de se propager sans générer une autre erreur.

Comportement réel
Lors de la capture d'un networkError , le champ response n'est pas défini, ce qui provoque des erreurs de syntaxe lors de la tentative de définition response.errors . Pour des raisons qui me dépassent pour le moment, cela empêche en fait l'erreur réseau de se propager, mais génère une autre erreur à la place.

Dans ma configuration (MacOS 10.14.1 avec Chrome 70.0.3538.77), le réglage response.errors = null lancera un Uncaught TypeError: Cannot set property 'errors' of undefined et response = { errors: null } lancera un Uncaught Error: "response" is read-only .

skaermbillede 2018-11-06 kl 15 08 10

skaermbillede 2018-11-06 kl 15 09 20

Une reproduction _simple_

Ce qui suit est une reproduction utilisant apollo-boost . Le même problème se produit en utilisant apollo-link-error directement.

import ApolloClient from 'apollo-boost';

const client = ApolloClient({
  uri: '/my_api',
  onError: (({ response, networkError }) => {
    if (networkError && networkError.statusCode === 401) {
      console.log(response); // prints 'undefined'
      response.errors = null; // will throw 'undefined' error
      response = { errors: null }; // will throw a 'read-only' error 
    }
  })
});

Étiquettes de problème

  • [ ] a-reproduction
  • [ ] caractéristique
  • [x] documents
  • [ ] blocage
  • [ ] bon premier numéro

docs

Commentaire le plus utile

Des avancées sur ce sujet ?

Tous les 29 commentaires

Existe-t-il une solution de contournement temporaire pour ce bogue ?

+1 un assez gros problème pour le débogage si vous utilisez le client apollo dans un environnement de serveur (lecture de données à partir d'un serveur GraphQL différent pour vos résolveurs).

+1
Existe-t-il une solution de contournement pour ignorer conditionnellement les erreurs ?

J'ai aussi ce problème.

Le gros problème pour moi est que cela empêche l'interface utilisateur d'être au courant de cette erreur. Il reste en état de chargement lorsqu'il y a une grosse erreur en fait.

une idée de la date de fusion ? :)

Probablement très bientôt, l'équipe apollo se concentre sur la publication du nouveau client et de la version réactive.

Des avancées sur ce sujet ?

Je me demande également s'il y a des progrès à ce sujet? Ou une solution de contournement en attendant? J'aimerais avoir un moyen de propager les messages d'erreur personnalisés de l'API dans l'interface utilisateur.

J'étais également confronté au même problème. La solution de contournement pour moi était de retourner Observable vide.

Je voulais juste rediriger discrètement vers la page de maintenance au cas où le serveur répondrait 503 - sans lever d'exception. Voici mon code, ça aidera peut-être quelqu'un :

import { Observable } from 'apollo-link';

...

onError(({ networkError }: any) => {
      if (networkError && networkError.status === 503) {
        this.redirectService.goToMaintenance();
        return Observable.of();
      }
});

...

@ luki215 je n'ai pas de champ de code d'erreur
image

@Sceat quel apollo-link utilisez-vous ? J'utilise apollo-angular-link-http , donc c'est peut-être la différence.

@ luki215 c'était en fait ma faute, en utilisant la passerelle AWS api, je n'ai pas correctement configuré la réponse CORS pour autre chose que les codes 200

pour les personnes rencontrant le problème, vous pouvez ajouter des réponses de passerelle API dans 4xx par défaut :
image

ou dans serverless.yml
yml GatewayDefault4XX: Type: 'AWS::ApiGateway::GatewayResponse' Properties: ResponseParameters: gatewayresponse.header.Access-Control-Allow-Origin: "'*'" gatewayresponse.header.Access-Control-Allow-Headers: "'*'" ResponseType: DEFAULT_4XX RestApiId: Ref: 'ApiGatewayRestApi'

Je crois que je rencontre le même point d'achoppement et que je suis incapable de transférer des messages dans mon application de réaction pour créer des messages d'erreur avec. Y a-t-il des progrès sur un correctif pour ce problème ?

@strass ne sais pas à propos de ce champ de réponse mais pour gérer les erreurs, vous pouvez activer l'erreur réseau

export default onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors) {
        for (let { extensions: { code } } of graphQLErrors)
            handleTheError(code)
    }
    if (networkError) {
        switch (networkError.statusCode) {
            case 500:
                //
                break
            case 429:
                //
                break
            case undefined:
                //
                break
            default:
                console.error(networkError)
                break
        }
    }
})

Je viens d'essayer la solution de contournement de @ luki215 consistant à renvoyer un Observable vide, mais dans mon cas, cela fera désormais réagir-apollo lancer une exception:

Uncaught (in promise) TypeError: Cannot read property 'data' of undefined
    at Mutation._this.onMutationCompleted (react-apollo.esm.js:627)
    at react-apollo.esm.js:564

J'utilise ceci pour muter l'erreur renvoyée au composant en utilisant le Mutation

const errorLink = onError(({ forward, networkError, operation }) => {
  if (networkError) {
    const { message: errorMessage } = networkError.result;
    switch (networkError.statusCode) {
      case 400:
        if (errorMessage) {
          networkError.message = errorMessage;
        }
        break;
      case 403:
        window.location = '/users/login';
        break;
      default:
        break;
    }
  }
  forward(operation);
});

Je pense que la clé que les gens recherchent peut-être ici est la possibilité de remplacer la chaîne d'erreur renvoyée dans networkError et c'est fait avec ce networkError.message = 'Some custom error message.'

Nous avons trouvé une solution de contournement hacky après avoir creusé dans apollo-link-rest et ce paquet. Plutôt que d'utiliser onError , j'ai réécrit le mien (ci-dessous).

L'intention : intercepter un NetworkError et le transformer conditionnellement en un GraphQLError . La raison étant que j'utilise un point de terminaison REST qui ne suit pas la convention GraphQL et renvoie des formes comme {"message": "{ \"key\": \"not valid\"}"} avec un code d'état 400. Cela ne devrait pas être traité comme une NetworkError.

Ce qui fonctionne: cette solution transmet le message d'erreur réel de la réponse à la chaîne, de manière à ce que je puisse le récupérer et l'analyser dans la promesse $ mutate catch et modifier l'état du formulaire enfant .

Ce qui ne marche pas :

  • Je préférerais utiliser observer.next({ data: { ... }, errors: { ... } }) pour transmettre les erreurs de la bonne manière, mais cela ne fonctionne pas (ces données ne vont nulle part et la promesse de mutation ne semble pas se résoudre).
  • appeler observer.next() et observer.error() en séquence n'a pas le comportement que j'attendais, suite à this . Seul l'appel d'erreur semble prendre effet.
const errorLink = new ApolloLink((operation, forward) => {
    return new Observable(observer => {
      let sub: ZenObservable.Subscription;

      try {
        sub = forward!(operation).subscribe({
          next: result => {
            console.log("A proper result looks like:", result);
            observer.next(result)
          },
          error: networkError => {
            try {
              const { message } = networkError.result;
              const error = new GraphQLError(message);
              const apolloError = new ApolloError({
                errorMessage: "Some error happened",
                graphQLErrors: [error],
                networkError: undefined,
              });

              observer.error(new Error(message));
            } catch (_) {
              observer.error(networkError)
            }
          },
          complete: () => {
            console.log("Calling complete()");
            observer.complete();
          },
        });
      } catch (e) {
        console.log("ErrorLink had an error of its own")
        observer.error(e);
      }

      return () => {
        if (sub) sub.unsubscribe();
      };
    });
  });

Il me semble que cela devrait fonctionner, mais la fonction mutate() ne semble pas résoudre ou rejeter - ni then() ni catch() ne sont appelés.

const errorLink = new ApolloLink((operation, forward) => {
    return new Observable(observer => {
      let sub: ZenObservable.Subscription;

      try {
        sub = forward!(operation).subscribe({
          next: result => {
            console.log("A proper result looks like:", result);
            observer.next(result)
          },
          error: networkError => {
            try {
              const { message } = networkError.result;
              const error = new GraphQLError(message);

              // this is called, but it seems to be the end of the line
              console.log("Intercepting error, returning success");
              observer.next({ data: {}, errors: [ error ]});
            } catch (localError) {
              console.error("Error in link:", localError);
              observer.error(networkError)
            }
          },
          complete: () => {
            console.log("Calling complete()");
            observer.complete();
          },
        });
      } catch (e) {
        console.log("ErrorLink had an error of its own")
        observer.error(e);
      }

      return () => {
        if (sub) sub.unsubscribe();
      };
    });
  });

Il suffit de pousser...
Trébucher sur la même erreur maintenant.
Vous voulez réinitialiser l'erreur dans le champ de réponse pour gérer l'erreur sur le composant lui-même...

Aucun progrès?

+1 sur le même problème.

Je pense avoir trouvé une solution (au moins à mon problème de propagation d'erreur):

Remplacer map par forEach dans errorLink signifie que les erreurs peuvent propager des erreurs à l'interface utilisateur (auparavant, result.error dans le composant UI n'était pas défini, maintenant il contient les erreurs):

const link = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors)
-    graphQLErrors.map(({ message, locations, path }) =>
+    graphQLErrors.forEach(({ message, locations, path }) =>
      console.log(
        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
      ),
    );

  if (networkError) console.log(`[Network error]: ${networkError}`);
});

J'étais également confronté au même problème. La solution de contournement pour moi était de retourner Observable vide.

Je voulais juste rediriger discrètement vers la page de maintenance au cas où le serveur répondrait 503 - sans lever d'exception. Voici mon code, ça aidera peut-être quelqu'un :

import { Observable } from 'apollo-link';

...

onError(({ networkError }: any) => {
      if (networkError && networkError.status === 503) {
        this.redirectService.goToMaintenance();
        return Observable.of();
      }
});

...

Cela a fonctionné pour moi, mais cela fait exactement le contraire de ce que dit la documentation, c'est-à-dire "Le rappel d'erreur peut éventuellement renvoyer un observable à partir de l'appel vers l'avant (opération) s'il veut réessayer la demande. Il ne devrait rien renvoyer d'autre."

Après la mise à jour d'Apollo aujourd'hui, ma solution ci-dessus ne comporte plus d'erreurs graphQL via des crochets apollo vers l'interface utilisateur.

Quelqu'un a-t-il une autre solution ?

@strass pouvez-vous expliquer pourquoi votre solution ci-dessus résout le problème. IMO, ils font exactement la même chose en utilisant le forEach et le map .

Ce n'est plus le cas (voir https://github.com/apollographeql/apollo-link/issues/855#issuecomment-538010335 )

Des progrès à ce sujet ?

Comment sommes-nous censés traiter un cas où le serveur renvoie un 302 avec un en-tête "Location" pour se ré-authentifier, si nous ne pouvons pas du tout obtenir l'objet de réponse ?

Si quelqu'un a la moindre idée, faites-le moi savoir parce que nous pensons laisser tomber Apollo :(

Cela m'a énormément aidé https://github.com/apollographql/apollo-link/issues/297#issuecomment -350488527 et permet de lire les réponses du réseau.

Quelque chose qui est crucial lorsque des réponses non autorisées sont renvoyées par les couches d'authentification/les API de passerelle.

J'aimerais voir cela dans le cadre de la fonctionnalité de base !

Des progrès à ce sujet ? Il n'y a aucun moyen que ce soit le comportement prévu. Sinon, comment peut-on transmettre les erreurs GraphQL au client avec un code d'état autre que 200 ?

Juste une note pour toute autre personne rencontrant un problème avec la capture et la modification des erreurs de réseau. J'essayais de déballer les erreurs de réseau dans l'erreur interne de graphql et voici ce que j'ai obtenu :

const errorHandler = onError(({ graphQLErrors, networkError, operation, forward, response }) => {
    if (graphQLErrors) {
      console.error('apollo errors', graphQLErrors);
    }
    if (networkError) {
      console.error('apollo network errors', networkError);
      if (!!networkError['error'] && !!networkError['error']['errors'] && networkError['error']['errors'][0]) {
        console.error('unwrapping apollo network errors');
        networkError.message = networkError['error']['errors'][0].message;
        // you may also be able to set networkError.message to null based on criteria to remove the error, even if you can't prevent an error from being triggered altogether
      }
    }
  }
);

// Create an http link:
const httpLink = new HttpLink(this.httpClient).create({
  uri: this.uri,
  headers: new HttpHeaders({
    Authorization: this.token
  }),
  includeExtensions: true
});

const httpErrorLink = ApolloLink.from([
  errorHandler,
  httpLink,
]);

Essentiellement, je remplace le message qui est un message d'erreur réseau générique par le premier message d'erreur interne. Cela pourrait aider certaines personnes comme moi qui se sont retrouvées ici.

Cette page vous a été utile?
0 / 5 - 0 notes