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
.
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
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.
J'ai examiné cela et selon la documentation, une réponse devrait être donnée au rappel :
https://www.apollographql.com/docs/link/links/error.html#callback
Avec le rappel normal, cela se produit : https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-error/src/index.ts#L46
Mais avec le networkError : https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-error/src/index.ts#L62
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
@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 :
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 :
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).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.
Commentaire le plus utile
Des avancées sur ce sujet ?