Apollo-link: El campo de respuesta no está definido para networkError [apollo-link-error]

Creado en 6 nov. 2018  ·  29Comentarios  ·  Fuente: apollographql/apollo-link


Parece que hay un error en la función onError o en su documentación. El problema ya se mencionó en el n.º 698, pero pensé en desarrollarlo un poco con la esperanza de una resolución.

Al interceptar un error usando onError desde apollo-boost o directamente desde apollo-link-error la documentación indica que el objeto de error contiene un objeto response , que se puede usar para ignorar errores.

He marcado la casilla de verificación de documentos ya que este podría ser el comportamiento previsto, pero como los documentos mencionan el objeto response en un subpárrafo de la sección networkError , probablemente debería aclararse un poco si eso es el caso.

Comportamiento esperado
La configuración response.errors = null; debería evitar que el error se propague sin generar otro error.

Comportamiento real
Al capturar un networkError el campo response no está definido, lo que provoca errores de sintaxis al intentar configurar response.errors . Por razones que están más allá de mí en este momento, esto realmente evita que el error de red se propague, pero arroja otro error en su lugar.

En mi configuración (MacOS 10.14.1 con Chrome 70.0.3538.77), la configuración response.errors = null arrojará un Uncaught TypeError: Cannot set property 'errors' of undefined y response = { errors: null } arrojará un Uncaught Error: "response" is read-only .

skaermbillede 2018-11-06 kl 15 08 10

skaermbillede 2018-11-06 kl 15 09 20

Una reproducción _simple_

La siguiente es una reproducción usando apollo-boost . El mismo problema ocurre usando apollo-link-error directamente.

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 
    }
  })
});

Emitir etiquetas

  • [ ] tiene-reproducción
  • [ ] rasgo
  • [x] documentos
  • [ ] bloqueo
  • [ ] buen primer número

docs

Comentario más útil

¿Algún progreso en este tema?

Todos 29 comentarios

¿Hay alguna solución temporal para este error?

+1 un problema bastante grande para la depuración si está utilizando el cliente apollo en el entorno del servidor (leyendo datos de un servidor GraphQL diferente para sus resolutores).

+1
¿Hay alguna solución para ignorar condicionalmente los errores?

También tengo este problema.

El gran problema para mí es que esto evita que la interfaz de usuario sea consciente de este error. Permanece en estado de carga cuando en realidad hay un gran error.

He investigado esto y, de acuerdo con los documentos, se debe dar una respuesta a la devolución de llamada:
https://www.apollographql.com/docs/link/links/error.html#callback

Con la devolución de llamada normal, esto sucede: https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-error/src/index.ts#L46

Pero con el error de red: https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-error/src/index.ts#L62

¿Alguna idea de cuándo se fusionará? :)

Probablemente muy pronto, el equipo de apollo se concentrará en lanzar el nuevo cliente y la versión de reacción.

¿Algún progreso en este tema?

También me pregunto si hay algún progreso en esto. ¿O una solución mientras tanto? Me gustaría tener una forma de propagar mensajes de error personalizados desde la API a la interfaz de usuario.

Yo también estaba enfrentando el mismo problema. La solución para mí fue devolver el Observable vacío.

Solo quería redirigir silenciosamente a la página de mantenimiento en caso de que el servidor responda 503, sin generar una excepción. Aquí está mi código, tal vez ayude a alguien:

import { Observable } from 'apollo-link';

...

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

...

@ luki215 no tengo ningún campo de código de error
image

@Sceat , ¿qué apollo-link usas? Uso apollo-angular-link-http , así que esa podría ser la diferencia.

@ luki215 en realidad fue mi culpa, al usar la puerta de enlace api de AWS no configuré correctamente la respuesta CORS para cualquier otra cosa que 200 códigos

para las personas que se encuentran con el problema, puede agregar las respuestas de la puerta de enlace api en 4xx predeterminado:
image

o en 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'

Creo que estoy llegando al mismo punto de tropiezo y no puedo reenviar mensajes a mi aplicación de reacción para crear mensajes de error. ¿Hay algún progreso en la solución de este problema?

@strass no sé acerca de este campo de respuesta, pero para manejar los errores, puede activar el error de red

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
        }
    }
})

Acabo de probar la solución alternativa de @ luki215 de devolver un Observable vacío, pero en mi caso eso ahora hará que react-apollo arroje una excepción:

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

Estoy usando esto para mutar el error devuelto al componente usando 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);
});

Creo que la clave que la gente quizás esté buscando aquí es la capacidad de anular la cadena de error devuelta en networkError y se hace con este networkError.message = 'Some custom error message.'

Se le ocurrió una solución alternativa después de investigar apollo-link-rest y este paquete. En lugar de usar onError , reescribí el mío (abajo).

La intención: interceptar un NetworkError y transformarlo condicionalmente en GraphQLError . La razón es que estoy usando un punto final REST que no sigue la convención de GraphQL y devuelve formas como {"message": "{ \"key\": \"not valid\"}"} con un código de estado 400. Eso no debe tratarse como un NetworkError.

Qué funciona: esta solución transmite el mensaje de error real de la respuesta a lo largo de la cadena, de manera que puedo recuperarlo y analizarlo dentro de la promesa $ mutate catch y modificar el estado del formulario secundario .

Lo que no funciona:

  • Preferiría usar observer.next({ data: { ... }, errors: { ... } }) para pasar los errores de la manera correcta, pero no funciona (esos datos no van a ninguna parte y la promesa de mutación no parece resolverse).
  • llamar a observer.next() y observer.error() en secuencia no tiene el comportamiento que esperaba, después de esto . Solo la llamada de error parece tener efecto.
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();
      };
    });
  });

Me parece que esto debería funcionar, pero la función mutate() no parece resolverse ni rechazarse; no se llama ni a then() ni a catch() .

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();
      };
    });
  });

Solo empujando...
Tropezando con el mismo error ahora.
Desea restablecer el error en el campo de respuesta para manejar el error en el componente en sí...

¿Cualquier progreso?

+1 en el mismo problema.

Creo que podría haber encontrado una solución (al menos para mi problema de propagación de errores):

Reemplazar map con forEach en errorLink significa que los errores pueden propagar errores a la interfaz de usuario (anteriormente, result.error en el componente de la interfaz de usuario no estaba definido, ahora tiene los errores):

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}`);
});

Yo también estaba enfrentando el mismo problema. La solución para mí fue devolver el Observable vacío.

Solo quería redirigir silenciosamente a la página de mantenimiento en caso de que el servidor responda 503, sin generar una excepción. Aquí está mi código, tal vez ayude a alguien:

import { Observable } from 'apollo-link';

...

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

...

Esto ha funcionado para mí, pero está haciendo exactamente lo contrario de lo que dice la documentación, es decir, "La devolución de llamada de error puede devolver opcionalmente un observable de llamar al reenvío (operación) si quiere volver a intentar la solicitud. No debería devolver nada más".

Después de actualizar Apollo hoy, mi solución anterior ya no transmite errores de graphQL a través de apollo-hooks a la interfaz de usuario.

¿Alguien tiene otra solución?

@strass , ¿puede explicar por qué su solución anterior soluciona el problema? En mi opinión, hacen exactamente lo mismo usando forEach y map .

Ya no lo hace (consulte https://github.com/apollographql/apollo-link/issues/855#issuecomment-538010335)

¿Algún progreso en esto?

¿Cómo se supone que debemos lidiar con un caso en el que el servidor devuelve un 302 con un encabezado de "Ubicación" para volver a autenticarse, si no podemos obtener el objeto de respuesta en absoluto?

Si alguien tiene alguna pista, hágamelo saber porque estamos pensando en dejar apolo :(

Esto me ayudó enormemente https://github.com/apollographql/apollo-link/issues/297#issuecomment -350488527 y permite leer las respuestas de la red.

Algo que es crucial cuando se devuelven respuestas no autorizadas desde las capas de autenticación/API de puerta de enlace.

¡Me encantaría ver esto como parte de la funcionalidad principal!

¿Algún progreso en esto? No hay forma de que este sea el comportamiento previsto. ¿De qué otra manera se pueden reenviar los errores de GraphQL al cliente con un código de estado que no sea 200?

Solo una nota para cualquier otra persona que tenga problemas para detectar y modificar errores de red. Estaba tratando de desenvolver los errores de red en el error interno de graphql y esto es lo que terminé:

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,
]);

Esencialmente, reemplazo el mensaje que es un mensaje de error de red genérico con el primer mensaje de error interno. Podría ayudar a algunas personas como yo que terminaron aquí.

¿Fue útil esta página
0 / 5 - 0 calificaciones