我有一个 graphql
/ apollo-server
/ graphql-yoga
终结点。此端点公开从数据库(或rest端点或其他服务)返回的数据。
我知道我的数据源正在返回正确的数据——如果我将调用数据源的结果记录在解析器中,我可以看到返回的数据。但是,我的graphql字段总是解析为null。
如果将字段设置为非null,则在 errors
响应中的数组:
对于不可为null的字段,不能返回null
为什么graphql不返回数据?
我有一个 graphql
/ apollo-server
/ graphql-yoga
终结点。此端点公开从数据库(或rest端点或其他服务)返回的数据。
我知道我的数据源正在返回正确的数据——如果我将调用数据源的结果记录在解析器中,我可以看到返回的数据。但是,我的graphql字段总是解析为null。
如果将字段设置为非null,则在 errors
响应中的数组:
对于不可为null的字段,不能返回null
为什么graphql不返回数据?
1条答案
按热度按时间mefy6pfw1#
字段解析为null有两个常见的原因:1)在解析程序中以错误的形式返回数据;2)没有正确使用承诺。
注意:如果您看到以下错误:
对于不可为null的字段,不能返回null
根本的问题是您的字段返回null。您仍然可以按照下面概述的步骤尝试解决此错误。
以下示例将引用此简单模式:
返回错误形状的数据
我们的模式和请求的查询一起定义了
data
在端点返回的响应中。所谓形状,我们指的是对象具有什么属性,以及这些属性的“值”是标量值、其他对象还是对象或标量的数组。与模式定义总响应的形状相同,单个字段的类型定义该字段值的形状。我们在解析器中返回的数据的形状也必须与预期的形状相匹配。如果没有,我们的响应常常会出现意外的空值。
不过,在深入研究具体示例之前,掌握graphql如何解析字段很重要。
了解默认解析程序行为
虽然您当然可以为模式中的每个字段编写一个解析器,但这通常不是必需的,因为graphql.js在您没有提供解析器时使用默认的解析器。
在较高的层次上,默认解析器所做的很简单:它查看父字段解析到的值,如果该值是javascript对象,则在该对象上查找与要解析的字段同名的属性。如果找到该属性,它将解析为该属性的值。否则,它将解析为null。
假设在我们的解析器中
post
字段,则返回值{ title: 'My First Post', bod: 'Hello World!' }
. 如果我们不为Post
类型,我们仍然可以请求post
:我们的React是
这个
title
字段被解析了,尽管我们没有为它提供解析程序,因为默认的解析程序完成了繁重的工作--它看到有一个名为title
对象上的父字段(在本例中post
)解析为,所以它只是解析为那个属性的值。这个id
字段解析为null,因为我们在post
解析程序没有id
财产。这个body
字段也被解析为null,因为输入错误--我们有一个名为bod
而不是body
!专业提示:如果
bod
不是输入错误,而是api或数据库实际返回的内容,我们总是可以为body
字段以匹配我们的架构。例如:(parent) => parent.bod
需要记住的一点是,在javascript中,几乎所有东西都是对象。所以如果post
字段解析为字符串或数字,即Post
类型仍将尝试在父对象上找到适当命名的属性,不可避免地会失败并返回null。如果一个字段有一个对象类型,但您在其解析程序中返回的不是对象(如字符串或数组),您将不会看到任何类型不匹配的错误,但该字段的子字段将不可避免地解析为null。常见场景#1: Package 响应
如果我们正在为
post
查询时,我们可能会从其他端点获取代码,如下所示:这个
post
字段的类型为Post
,所以我们的解析器应该返回一个具有如下属性的对象id
,title
以及body
. 如果这是我们的api返回的结果,那么我们都准备好了。但是,通常情况下,响应实际上是一个包含额外元数据的对象。因此,我们从端点实际得到的对象可能如下所示:在这种情况下,我们不能只按原样返回响应并期望默认解析器正常工作,因为我们返回的对象没有
id
,title
以及body
我们需要的财产。我们的解析器不需要执行以下操作:注意:上面的示例从另一个端点获取数据;但是,当直接使用数据库驱动程序(与使用orm相反)时,这种 Package 响应也是非常常见的!例如,如果您使用的是node postgres,那么
Result
包含属性的对象,如rows
,fields
,rowCount
以及command
. 在将响应返回到解析器之前,需要从该响应中提取适当的数据。常见场景#2:数组代替对象
如果我们从数据库中获取一个post,解析程序可能如下所示:
哪里
Post
是我们通过上下文注入的模型。如果我们使用sequelize
,我们可以打电话findAll
.mongoose
以及typeorm
有find
. 这些方法的共同点是,虽然它们允许我们指定WHERE
条件下,它们返回的承诺仍然解析为数组而不是单个对象。虽然您的数据库中可能只有一个具有特定id的post,但当您调用其中一个方法时,它仍然 Package 在一个数组中。因为数组仍然是一个对象,所以graphql不会解析post
字段为空。但它会将所有子字段解析为null,因为它无法在数组中找到适当命名的属性。您只需抓取数组中的第一项并将其返回到解析器中,就可以轻松地修复此情况:
如果您从另一个api获取数据,这通常是唯一的选择。另一方面,如果您使用的是orm,那么通常可以使用不同的方法(例如
findOne
)它将显式地只从db返回一行(如果不存在则返回null)。关于
INSERT
以及UPDATE
调用:我们通常期望插入或更新行或模型示例的方法返回插入或更新的行。他们经常这样做,但有些方法不行。例如,sequelize
的upsert
方法解析为一个布尔值或上插入的记录的元组和一个布尔值(如果returning
选项设置为true)。mongoose
的findOneAndUpdate
解析为具有value
包含已修改行的属性。在将结果返回到解析器之前,请查阅orm的文档并对结果进行适当的解析。常见场景#3:对象代替数组
在我们的模式中
posts
字段的类型是List
的Post
s、 这意味着它的解析器需要返回一个对象数组(或解析为一个对象的承诺)。我们可以这样取回帖子:但是,我们的api的实际响应可能是一个 Package post数组的对象:
无法在解析程序中返回此对象,因为graphql需要数组。如果我们这样做,字段将解析为null,并且我们将在响应中看到一个错误,如:
应为iterable,但未找到字段query.posts的iterable。
与上述两种情况不同,在本例中,graphql能够显式检查我们在解析器中返回的值的类型,如果它不是数组那样的iterable,它将抛出。
正如我们在第一个场景中讨论的,为了修复此错误,我们必须将响应转换为适当的形状,例如:
不正确使用承诺
js在幕后使用了promise api。因此,解析器可以返回一些值(如
{ id: 1, title: 'Hello!' }
)或者它可以返回一个承诺,将解决这个价值。对于具有List
输入时,还可以返回一个承诺数组。如果承诺被拒绝,则该字段将返回null,并且相应的错误将添加到errors
响应中的数组。如果字段具有对象类型,则promise解析为的值将作为父值传递给任何子字段的解析程序。promise是“表示异步操作的最终完成(或失败)及其结果值的对象”。下面几个场景概述了在解析程序中处理promise时遇到的一些常见陷阱。但是,如果您不熟悉promises和较新的async/await语法,强烈建议您花一些时间阅读基础知识。
注意:下面的几个例子涉及
getPost
功能。这个函数的实现细节并不重要——它只是一个返回承诺的函数,它将解析为post对象。常见场景4:不返回值
用于
post
字段可能如下所示:function post(root, args) {
getPost(args.id)
}