Saltar al contenido

¿Por qué una consulta GraphQL devuelve nulo?

Bienvenido a nuestro sitio web, en este lugar encontrarás la resolución de lo que estás buscando.

Solución:

Hay dos razones comunes por las que su campo o campos se resuelven como nulos: 1) devolviendo datos en la forma incorrecta dentro de su resolución; y 2) no usar Promesas correctamente.

Nota: si ve el siguiente error:

No se puede devolver un valor nulo para un campo que no admite valores NULL

el problema subyacente es que su campo devuelve nulo. Aún puede seguir los pasos que se describen a continuación para intentar resolver este error.

Los siguientes ejemplos se referirán a este esquema simple:

type Query 
  post(id: ID): Post
  posts: [Post]


type Post 
  id: ID
  title: String
  body: String

Devolver datos en forma incorrecta

Nuestro esquema, junto con la consulta solicitada, define la “forma” de la data objeto en la respuesta devuelta por nuestro punto final. Por forma, nos referimos a las propiedades que tienen los objetos y si esos ‘valores’ de propiedades son valores escalares, otros objetos o matrices de objetos o escalares.

De la misma manera que un esquema define la forma de la respuesta total, la escribe de un campo individual define la forma del valor de ese campo. La forma de los datos que devolvemos en nuestro resolutor también debe coincidir con esta forma esperada. Cuando no es así, con frecuencia terminamos con nulos inesperados en nuestra respuesta.

Sin embargo, antes de sumergirnos en ejemplos específicos, es importante comprender cómo GraphQL resuelve campos.

Comprender el comportamiento de resolución predeterminado

Mientras que ciertamente pueden escriba un resolutor para cada campo en su esquema, a menudo no es necesario porque GraphQL.js usa un resolutor predeterminado cuando usted no proporciona uno.

En un nivel alto, lo que hace el solucionador predeterminado es simple: mira el valor del padre campo resuelto y si ese valor es un objeto JavaScript, busca una propiedad en ese objeto con el mismo nombre como el campo que se está resolviendo. Si encuentra esa propiedad, se resuelve en el valor de esa propiedad. De lo contrario, se resuelve como nulo.

Digamos en nuestro solucionador para el post campo, devolvemos el valor title: 'My First Post', bod: 'Hello World!' . Si no escribimos resolutores para ninguno de los campos en el Post tipo, todavía podemos solicitar el post:

query 
  post 
    id
    title
    body
  

y nuestra respuesta será


  "data": 
    "post" 
      "id": null,
      "title": "My First Post",
      "body": null,
    
  

los title El campo se resolvió a pesar de que no proporcionamos un resolutor para él porque el resolutor predeterminado hizo el trabajo pesado: vio que había una propiedad llamada title en el Objeto el campo padre (en este caso post) se resolvió y, por lo tanto, se resolvió al valor de esa propiedad. los id El campo se resolvió en nulo porque el objeto que devolvimos en nuestro post resolver no tenía un id propiedad. los body El campo también se resolvió en nulo debido a un error tipográfico: tenemos una propiedad llamada bod en lugar de body!

Consejo profesional: Si bod es no un error tipográfico, pero lo que realmente devuelve una API o una base de datos, siempre podemos escribir un resolutor para el body campo para que coincida con nuestro esquema. Por ejemplo: (parent) => parent.bod

Una cosa importante a tener en cuenta es que en JavaScript, casi todo es un objeto. Entonces si el post El campo se resuelve en una Cadena o un Número, el resolutor predeterminado para cada uno de los campos en el Post type todavía intentará encontrar una propiedad con el nombre apropiado en el objeto principal, inevitablemente fallará y devolverá un valor nulo. Si un campo tiene un tipo de objeto pero devuelve algo que no sea un objeto en su resolución (como una cadena o una matriz), no verá ningún error sobre la falta de coincidencia de tipos, pero los campos secundarios para ese campo inevitablemente se resolverán como nulos.

Escenario común n. ° 1: respuestas envueltas

Si estamos escribiendo el resolutor para el post consulta, podríamos obtener nuestro código de algún otro punto final, como este:

function post (root, args) 
  // axios
  return axios.get(`http://SOME_URL/posts/$args.id`)
    .then(res => res.data);

  // fetch
  return fetch(`http://SOME_URL/posts/$args.id`)
    .then(res => res.json());

  // request-promise-native
  return request(
    uri: `http://SOME_URL/posts/$args.id`,
    json: true
  );

los post el campo tiene el tipo Post, por lo que nuestro solucionador debería devolver un objeto con propiedades como id, title y body. Si esto es lo que devuelve nuestra API, estamos listos. Sin embargo, es común que la respuesta sea en realidad un objeto que contiene metadatos adicionales. Entonces, el objeto que realmente obtenemos del punto final podría verse así:


  "status": 200,
  "result": 
    "id": 1,
    "title": "My First Post",
    "body": "Hello world!"
  ,

En este caso, no podemos simplemente devolver la respuesta como está y esperar que el solucionador predeterminado funcione correctamente, ya que el objeto que estamos devolviendo no tiene la id , title y body propiedades que necesitamos. Nuestro resolutor no necesita hacer algo como:

function post (root, args) 
  // axios
  return axios.get(`http://SOME_URL/posts/$args.id`)
    .then(res => res.data.result);

  // fetch
  return fetch(`http://SOME_URL/posts/$args.id`)
    .then(res => res.json())
    .then(data => data.result);

  // request-promise-native
  return request(
    uri: `http://SOME_URL/posts/$args.id`,
    json: true
  )
    .then(res => res.result);

Nota: El ejemplo anterior obtiene datos de otro punto final; sin embargo, este tipo de respuesta ajustada también es muy común cuando se usa un controlador de base de datos directamente (en lugar de usar un ORM). Por ejemplo, si está utilizando node-postgres, obtendrá un Result objeto que incluye propiedades como rows, fields, rowCount y command. Deberá extraer los datos apropiados de esta respuesta antes de devolverlos dentro de su solucionador.

Escenario común n. ° 2: matriz en lugar de objeto

¿Qué pasa si obtenemos una publicación de la base de datos, nuestro solucionador podría verse así:

function post(root, args, context) 
  return context.Post.find( where:  id: args.id  )

dónde Post es un modelo que estamos inyectando a través del contexto. Si estamos usando sequelize, podríamos llamar findAll. mongoose y typeorm tengo find. Lo que estos métodos tienen en común es que, si bien nos permiten especificar un WHERE condición, las promesas vuelven todavía se resuelve en una matriz en lugar de un solo objeto. Si bien es probable que solo haya una publicación en su base de datos con una ID en particular, todavía está envuelta en una matriz cuando llama a uno de estos métodos. Debido a que una matriz sigue siendo un objeto, GraphQL no resolverá la post campo como nulo. Pero voluntad resuelva todos los campos secundarios como nulos porque no podrá encontrar las propiedades con el nombre apropiado en la matriz.

Puede solucionar fácilmente este escenario simplemente tomando el primer elemento de la matriz y devolviéndolo en su solucionador:

function post(root, args, context) 
  return context.Post.find( where:  id: args.id  )
    .then(posts => posts[0])

Si está obteniendo datos de otra API, esta suele ser la única opción. Por otro lado, si está usando un ORM, a menudo hay un método diferente que puede usar (como findOne) que devolverá explícitamente solo una fila de la base de datos (o nula si no existe).

function post(root, args, context) 
  return context.Post.findOne( where:  id: args.id  )

Una nota especial sobre INSERT y UPDATE llamadas: A menudo esperamos que los métodos que insertan o actualizan una fila o instancia de modelo devuelvan la fila insertada o actualizada. A menudo lo hacen, pero algunos métodos no lo hacen. Por ejemplo, sequelize‘s upsert El método se resuelve en un booleano, o tupla del registro insertado y un booleano (si el returning opción está establecida en verdadero). mongoose‘s findOneAndUpdate se resuelve en un objeto con un value propiedad que contiene la fila modificada. Consulte la documentación de su ORM y analice el resultado adecuadamente antes de devolverlo dentro de su resolutor.

Escenario común n. ° 3: objeto en lugar de matriz

En nuestro esquema, el posts el tipo de campo es un List de Posts, lo que significa que su solucionador necesita devolver una matriz de objetos (o una promesa que se resuelve en uno). Podríamos buscar las publicaciones como esta:

function posts (root, args) 
  return fetch('http://SOME_URL/posts')
    .then(res => res.json())

Sin embargo, la respuesta real de nuestra API podría ser un objeto que envuelve la matriz de publicaciones:


  "count": 10,
  "next": "http://SOME_URL/posts/?page=2",
  "previous": null,
  "results": [
    
      "id": 1,
      "title": "My First Post",
      "body" "Hello World!"
    ,
    ...
  ]

No podemos devolver este objeto en nuestro solucionador porque GraphQL espera un Array. Si lo hacemos, el campo se resolverá como nulo y veremos un error incluido en nuestra respuesta como:

Se esperaba Iterable, pero no se encontró uno para el campo Query.posts.

A diferencia de los dos escenarios anteriores, en este caso GraphQL puede verificar explícitamente el tipo de valor que devolvemos en nuestro resolutor y arrojará si no es un Iterable como un Array.

Como discutimos en el primer escenario, para corregir este error, tenemos que transformar la respuesta en la forma apropiada, por ejemplo:

function posts (root, args) 
  return fetch('http://SOME_URL/posts')
    .then(res => res.json())
    .then(data => data.results)

No usar las promesas correctamente

GraphQL.js hace uso de la API Promise bajo el capó. Como tal, un resolutor puede devolver algún valor (como id: 1, title: 'Hello!' ) o puede devolver una Promesa que resolver a ese valor. Para campos que tienen un List escriba, también puede devolver una matriz de Promesas. Si una Promesa se rechaza, ese campo devolverá un valor nulo y el error correspondiente se agregará al errors matriz en la respuesta. Si un campo tiene un tipo de Objeto, el valor al que se resuelve la Promesa es lo que se transmitirá como el valor de los padres a los resolutores de cualquier campo hijo.

Una Promesa es un “objeto que representa la eventual finalización (o falla) de una operación asincrónica y su valor resultante”. Los siguientes escenarios describen algunos errores comunes que se encuentran al tratar con Promesas dentro de los resolutores. Sin embargo, si no está familiarizado con Promises y la nueva sintaxis async / await, se recomienda encarecidamente que dedique un tiempo a leer sobre los fundamentos.

Nota: los siguientes ejemplos se refieren a getPost función. Los detalles de implementación de esta función no son importantes, es solo una función que devuelve una Promesa, que se resolverá en un objeto de publicación.

Escenario común n. ° 4: no devolver un valor

Un solucionador de trabajo para el post El campo podría verse así:

function post(root, args) 
  return getPost(args.id)

getPosts devuelve una Promesa y nosotros le devolvemos esa Promesa. Lo que sea que resuelva esa Promesa se convertirá en el valor que resuelva nuestro campo. ¡Luciendo bien!

Pero que pasa si hacemos esto:

function post(root, args) 
  getPost(args.id)

Todavía estamos creando una Promesa que se convertirá en una publicación. Sin embargo, no devolveremos la Promesa, por lo que GraphQL no la conoce y no esperará a que se resuelva. En funciones de JavaScript sin un explícito return declaración implícitamente retorno undefined. Entonces nuestra función crea una Promesa y luego regresa inmediatamente undefined, lo que hace que GraphQL devuelva un valor nulo para el campo.

Si la Promesa regresó por getPost rechaza, tampoco veremos ningún error en nuestra respuesta; debido a que no devolvimos la Promesa, al código subyacente no le importa si se resuelve o rechaza. De hecho, si la Promesa se rechaza, verá una
UnhandledPromiseRejectionWarning
en la consola de su servidor.

Solucionar este problema es simple: solo agregue el return.

Escenario común n. ° 5: no encadenar las promesas correctamente

Decide registrar el resultado de su llamada a getPost, por lo que cambia su resolutor para que se parezca a esto:

function post(root, args) 
  return getPost(args.id)
    .then(post => 
      console.log(post)
    )

Cuando ejecuta su consulta, ve el resultado registrado en su consola, pero GraphQL resuelve el campo a nulo. ¿Por qué?

Cuando llamamos then en una Promesa, estamos tomando efectivamente el valor que la Promesa resolvió y devolviendo una nueva Promesa. Puedes pensar en ello como Array.map excepto promesas. then puede devolver un valor u otra Promesa. En cualquier caso, lo que se devuelve dentro de then está “encadenado” a la Promesa original. Varias promesas se pueden encadenar juntas de esta manera utilizando múltiples thens. Cada Promesa en la cadena se resuelve en secuencia, y el valor final es lo que efectivamente se resuelve como el valor de la Promesa original.

En nuestro ejemplo anterior, no devolvimos nada dentro del then, por lo que la Promesa resolvió undefined, que GraphQL convirtió en nulo. Para solucionar esto, tenemos que devolver las publicaciones:

function post(root, args) 
  return getPost(args.id)
    .then(post => 
      console.log(post)
      return post // <----
    )

Si tiene varias promesas que necesita resolver dentro de su resolutor, debe encadenarlas correctamente utilizando then y devolviendo el valor correcto. Por ejemplo, si necesitamos llamar a otras dos funciones asincrónicas (getFoo y getBar) antes de que podamos llamar getPost, podemos hacer:

function post(root, args) {
  return getFoo()
    .then(foo => 
      // Do something with foo
      return getBar() // return next Promise in the chain
    )
    .then(bar => 
      // Do something with bar
      return getPost(args.id) // return next Promise in the chain
    )

Consejo profesional: Si tiene dificultades para encadenar correctamente las Promesas, es posible que la sintaxis async / await sea más limpia y más fácil de trabajar.

Escenario común n. ° 6

Antes de Promises, la forma estándar de manejar el código asincrónico era usar devoluciones de llamada, o funciones que se llamarían una vez que se completara el trabajo asincrónico. Podríamos, por ejemplo, llamar mongoose's findOne método como este:

function post(root, args) {
  return Post.findOne( where:  id: args.id  , function (err, post) 
    return post
  )

El problema aquí es doble. Uno, un valor que se devuelve dentro de una devolución de llamada no se usa para nada (es decir, no se pasa al código subyacente de ninguna manera). Dos, cuando usamos una devolución de llamada, Post.findOne no devuelve una promesa; simplemente devuelve indefinido. En este ejemplo, se llamará a nuestra devolución de llamada, y si registramos el valor de post veremos lo que se devolvió de la base de datos. Sin embargo, debido a que no usamos una Promesa, GraphQL no espera a que se complete esta devolución de llamada; toma el valor de retorno (indefinido) y lo usa.

Las bibliotecas más populares, incluidas mongoose Promesas de apoyo listas para usar. Aquellos que no suelen tener bibliotecas "contenedoras" complementarias que agregan esta funcionalidad. Cuando trabaje con resolutores GraphQL, debe evitar el uso de métodos que utilicen una devolución de llamada y, en su lugar, use los que devuelvan Promesas.

Consejo profesional: Las bibliotecas que admiten tanto devoluciones de llamada como promesas con frecuencia sobrecargan sus funciones de tal manera que si no se proporciona una devolución de llamada, la función devolverá una promesa. Consulte la documentación de la biblioteca para obtener más detalles.

Si es absolutamente necesario utilizar una devolución de llamada, también puede envolver la devolución de llamada en una Promesa:

function post(root, args) {
  return new Promise((resolve, reject) => 
    Post.findOne( where:  id: args.id  , function (err, post) 
      if (err) 
        reject(err)
       else 
        resolve(post)
      
    )
  )

Si para ti ha sido de utilidad nuestro artículo, sería de mucha ayuda si lo compartieras con más seniors y nos ayudes a difundir esta información.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *