Apollo-link: O campo de resposta está indefinido para networkError [apollo-link-error]

Criado em 6 nov. 2018  ·  29Comentários  ·  Fonte: apollographql/apollo-link


Parece haver um bug na função onError ou em sua documentação. O problema já foi mencionado no nº 698, mas pensei em aprofundar um pouco na esperança de uma resolução.

Ao interceptar um erro usando onError de apollo-boost ou diretamente de apollo-link-error a documentação afirma que o objeto de erro contém um objeto response , que pode ser usado para ignorar erros.

Marquei a caixa de seleção de documentos , pois isso pode ser um comportamento pretendido, mas como os documentos mencionam o objeto response em um subparágrafo da seção networkError , provavelmente deve ser esclarecido um pouco se for O caso.

Comportamento esperado
A configuração response.errors = null; deve evitar que o erro se propague sem gerar outro erro.

Comportamento real
Ao capturar um networkError o campo response fica indefinido, causando erros de sintaxe ao tentar definir response.errors . Por razões que estão além de mim no momento, isso realmente impede que o erro de rede se propague, mas gera outro erro.

Na minha configuração (MacOS 10.14.1 w. Chrome 70.0.3538.77), a configuração response.errors = null lançará um Uncaught TypeError: Cannot set property 'errors' of undefined e response = { errors: null } lançará um Uncaught Error: "response" is read-only .

skaermbillede 2018-11-06 kl 15 08 10

skaermbillede 2018-11-06 kl 15 09 20

Uma reprodução _simples_

O seguinte é uma reprodução usando apollo-boost . O mesmo problema ocorre usando apollo-link-error diretamente.

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

Etiquetas de emissão

  • [ ] tem-reprodução
  • [ ] funcionalidade
  • [x] documentos
  • [ ] bloqueando
  • [ ] boa primeira edição

docs

Comentários muito úteis

Algum progresso nesta questão?

Todos 29 comentários

Existe alguma solução temporária para este bug?

+1 um grande problema para depuração se você estiver usando o cliente apollo no ambiente do servidor (lendo dados de um servidor GraphQL diferente para seus resolvedores).

+1
Existe alguma solução para ignorar condicionalmente os erros?

Eu também tenho esse problema.

O grande problema para mim é que isso impede que a interface do usuário esteja ciente desse erro. Permanece no estado de carregamento quando há um grande erro na verdade.

alguma dica sobre quando ele será mesclado? :)

Provavelmente muito em breve, a equipe do apollo está focada em lançar o novo cliente e a versão react.

Algum progresso nesta questão?

Eu também estou querendo saber se há algum progresso sobre isso? Ou uma solução nesse meio tempo? Eu gostaria de ter uma maneira de propagar mensagens de erro personalizadas da API para a interface do usuário.

Eu também estava enfrentando o mesmo problema. A solução alternativa para mim era retornar Observable vazio.

Eu só queria redirecionar silenciosamente para a página de manutenção caso o servidor responda 503 - sem lançar exceção. Segue meu código, talvez ajude alguém:

import { Observable } from 'apollo-link';

...

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

...

@luki215 eu não tenho nenhum campo de código de erro
image

@Sceat qual apollo-link você usa? Eu uso apollo-angular-link-http , então essa pode ser a diferença.

@luki215 na verdade foi minha culpa, usando o AWS api gateway eu não configurei corretamente a resposta CORS para qualquer outra coisa que 200 códigos

para pessoas que estão enfrentando o problema, você pode adicionar respostas de gateway de API no padrão 4xx:
image

ou em 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'

Acredito que estou atingindo o mesmo ponto de tropeço e não consigo encaminhar mensagens para meu aplicativo de reação para criar mensagens de erro. Existe algum progresso em uma correção para este problema?

@strass não sei sobre este campo de resposta, mas para lidar com erros, você pode ativar o erro de rede

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

Acabei de tentar a solução alternativa do @luki215 de retornar um Observable vazio, mas no meu caso isso agora fará com que o react-apollo lance uma exceção:

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

Estou usando isso para alterar o erro retornado ao componente usando o 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);
});

Acho que a principal coisa que as pessoas talvez estejam procurando aqui é a capacidade de substituir a string de erro retornada em networkError e é feito com isso networkError.message = 'Some custom error message.'

Veio com uma solução hacky depois de pesquisar apollo-link-rest e este pacote. Em vez de usar onError , reescrevi o meu próprio (abaixo).

A intenção: interceptar um NetworkError e transformá-lo condicionalmente em GraphQLError . O motivo é que estou usando um endpoint REST que não segue a convenção do GraphQL e retorna formas como {"message": "{ \"key\": \"not valid\"}"} com um código de status 400. Isso não deve ser tratado como um NetworkError.

O que funciona: esta solução passa a mensagem de erro real da resposta para baixo na cadeia, de forma que eu possa recuperá-la e analisá-la dentro da promessa $ mutate catch e modificar o estado do formulário filho .

O que não funciona:

  • Eu preferiria usar observer.next({ data: { ... }, errors: { ... } }) para passar os erros da maneira certa, mas não funciona (esses dados não chegam a lugar algum e a promessa de mutação não parece resolver).
  • chamar observer.next() e observer.error() em sequência não tem o comportamento que eu esperava, seguindo este . Apenas a chamada de erro parece ter efeito.
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();
      };
    });
  });

Parece-me que isso deve funcionar, mas a função mutate() não parece resolver ou rejeitar - nem then() nem catch() são chamados.

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

Só empurrando...
Tropeçando no mesmo erro agora.
Deseja redefinir o erro no campo de resposta para lidar com o erro no próprio componente...

Algum progresso?

+1 no mesmo problema.

Acho que encontrei uma solução (pelo menos para o meu problema de propagação de erros):

Substituir map por forEach em errorLink significa que os erros podem propagar erros para a interface do usuário (anteriormente result.error no componente da interface do usuário era indefinido, agora tem o(s) erro(s):

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

Eu também estava enfrentando o mesmo problema. A solução alternativa para mim era retornar Observable vazio.

Eu só queria redirecionar silenciosamente para a página de manutenção caso o servidor responda 503 - sem lançar exceção. Segue meu código, talvez ajude alguém:

import { Observable } from 'apollo-link';

...

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

...

Isso funcionou para mim, mas está fazendo exatamente o oposto do que a documentação diz, ou seja, "O callback de erro pode opcionalmente retornar um observável de chamar forward(operation) se quiser tentar novamente a solicitação. Ele não deve retornar mais nada."

Depois de atualizar o Apollo hoje, minha solução acima não carrega mais erros graphQL por meio de apollo-hooks para a interface do usuário.

Alguém tem outra solução?

@strass você pode explicar por que sua solução acima corrige o problema. IMO eles fazem exatamente a mesma coisa usando o forEach e o map .

Não funciona mais (consulte https://github.com/apollographql/apollo-link/issues/855#issuecomment-538010335 )

Algum progresso nisso?

Como devemos lidar com um caso em que o servidor está retornando um 302 com um cabeçalho "Location" para reautenticar, se não conseguirmos obter o objeto de resposta?

Se alguém tiver alguma pista me avise porque estamos pensando em largar o apollo :(

Isso me ajudou muito https://github.com/apollographql/apollo-link/issues/297#issuecomment -350488527 e permite que as respostas da rede sejam lidas.

Algo que é crucial quando há respostas não autorizadas retornadas de camadas de autenticação/APIs de gateway.

Eu adoraria ver isso como parte da funcionalidade principal!

Algum progresso nisso? Não há nenhuma maneira este é o comportamento pretendido. De que outra forma alguém pode encaminhar erros do GraphQL para o cliente com um código de status diferente de 200?

Apenas uma nota para qualquer pessoa que tenha problemas com a captura e modificação de erros de rede. Eu estava tentando desempacotar erros de rede para o erro interno do graphql e aqui está o que acabei com:

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

Essencialmente, substituo a mensagem que é uma mensagem de erro de rede genérica pela primeira mensagem de erro interna. Pode ajudar algumas pessoas como eu que acabaram aqui.

Esta página foi útil?
0 / 5 - 0 avaliações