为什么GraphQL查询返回null?
我有一个为什么GraphQL查询返回null?,graphql,graphql-js,apollo-server,Graphql,Graphql Js,Apollo Server,我有一个graphql/apollo服务器/graphql瑜伽端点。此端点公开从数据库(或REST端点或其他服务)返回的数据 我知道我的数据源正在返回正确的数据——如果我将调用数据源的结果记录在解析器中,我可以看到返回的数据。但是,我的GraphQL字段始终解析为null 如果将字段设置为非null,则在响应中的errors数组中会看到以下错误: 对于不可为null的字段,无法返回null GraphQL为什么不返回数据?有两个常见的原因导致字段解析为空:1)在解析程序中以错误的形状返回数据;2
graphql
/apollo服务器
/graphql瑜伽
端点。此端点公开从数据库(或REST端点或其他服务)返回的数据
我知道我的数据源正在返回正确的数据——如果我将调用数据源的结果记录在解析器中,我可以看到返回的数据。但是,我的GraphQL字段始终解析为null
如果将字段设置为非null,则在响应中的errors
数组中会看到以下错误:
对于不可为null的字段,无法返回null
GraphQL为什么不返回数据?有两个常见的原因导致字段解析为空:1)在解析程序中以错误的形状返回数据;2)没有正确使用承诺 注意:如果您看到以下错误: 对于不可为null的字段,无法返回null 根本问题是您的字段返回null。您仍然可以按照下面概述的步骤尝试解决此错误 以下示例将引用此简单模式:
type Query {
post(id: ID): Post
posts: [Post]
}
type Post {
id: ID
title: String
body: String
}
以错误的形状返回数据
我们的模式以及请求的查询在端点返回的响应中定义了数据
对象的“形状”。所谓形状,我们指的是对象具有哪些属性,以及这些属性“值”是标量值、其他对象还是对象数组或标量
与模式定义总响应的形状的方式相同,单个字段的类型定义该字段值的形状。我们在解析器中返回的数据的形状也必须与预期的形状相匹配。如果没有,我们的响应中经常会出现意外的空值
不过,在深入研究具体示例之前,了解GraphQL如何解析字段是很重要的
理解默认解析器行为
虽然您当然可以为模式中的每个字段编写冲突解决程序,但这通常不是必需的,因为GraphQL.js在您不提供冲突解决程序时使用默认的冲突解决程序
从较高的层次来看,默认解析程序的作用很简单:它查看父字段解析为的值,如果该值是JavaScript对象,则在该对象上查找与解析字段同名的属性。如果找到该属性,它将解析为该属性的值。否则,它将解析为null
假设在post
字段的解析器中,我们返回值{title:'myfirst post',bod:'Hello World!'
。如果我们没有为Post
类型上的任何字段编写解析器,我们仍然可以请求Post
:
query {
post {
id
title
body
}
}
我们的反应是
{
"data": {
"post" {
"id": null,
"title": "My First Post",
"body": null,
}
}
}
title
字段已解析,尽管我们没有为其提供解析程序,因为默认解析程序执行了繁重的操作——它看到父字段(在本例中为post
)解析到的对象上有一个名为title
的属性,因此它只解析为该属性的值。id
字段解析为null,因为我们在post
解析程序中返回的对象没有id
属性。由于输入错误,body
字段也解析为null——我们有一个名为bod
的属性,而不是body
Pro-tip:如果bod
不是输入错误,而是API或数据库实际返回的内容,那么我们总是可以为body
字段编写一个解析器,以匹配我们的模式。例如:(parent)=>parent.bod
需要记住的一点是,在JavaScript中,几乎所有东西都是对象。因此,如果post
字段解析为字符串或数字,则post
类型上每个字段的默认解析程序仍将尝试在父对象上查找适当命名的属性,不可避免地会失败并返回null。如果字段具有对象类型,但在其解析程序中返回的不是对象(如字符串或数组),则不会看到任何类型不匹配的错误,但该字段的子字段将不可避免地解析为null
常见场景#1:包装响应
如果我们正在为post
查询编写解析器,我们可能会从其他端点获取代码,如下所示:
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
});
}
{
"status": 200,
"result": {
"id": 1,
"title": "My First Post",
"body": "Hello world!"
},
}
function post(root, args, context) {
return context.Post.find({ where: { id: args.id } })
}
function posts (root, args) {
return fetch('http://SOME_URL/posts')
.then(res => res.json())
}
function post(root, args) {
return getPost(args.id)
}
function post(root, args) {
return getPost(args.id)
.then(post => {
console.log(post)
})
}
function post(root, args) {
return Post.findOne({ where: { id: args.id } }, function (err, post) {
return post
})
post
字段的类型为post
,因此我们的解析器应该返回一个具有id
、title
和body
等属性的对象。如果这是我们的API返回的结果,那么我们都准备好了然而,通常响应实际上是一个包含额外元数据的对象。因此,我们实际上从端点返回的对象可能如下所示:
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
});
}
{
"status": 200,
"result": {
"id": 1,
"title": "My First Post",
"body": "Hello world!"
},
}
function post(root, args, context) {
return context.Post.find({ where: { id: args.id } })
}
function posts (root, args) {
return fetch('http://SOME_URL/posts')
.then(res => res.json())
}
function post(root, args) {
return getPost(args.id)
}
function post(root, args) {
return getPost(args.id)
.then(post => {
console.log(post)
})
}
function post(root, args) {
return Post.findOne({ where: { id: args.id } }, function (err, post) {
return post
})
在这种情况下,我们不能按原样返回响应并期望默认解析器正常工作,因为我们返回的对象没有我们需要的id
、title
和body
属性。我们的解析器不需要执行以下操作:
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);
}
注意:上述示例从另一个端点获取数据;然而,当直接使用数据库驱动程序时(与使用ORM相反),这种包装响应也是非常常见的!例如,如果正在使用,您将得到一个结果
对象,该对象包括行
、字段
、行数
和命令
等属性。在将响应返回到解析器中之前,需要从该响应中提取适当的数据
常见场景#2:数组而不是对象
如果我们从数据库中获取帖子,我们的解析器可能会如下所示:
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
});
}
{
"status": 200,
"result": {
"id": 1,
"title": "My First Post",
"body": "Hello world!"
},
}
function post(root, args, context) {
return context.Post.find({ where: { id: args.id } })
}
function posts (root, args) {
return fetch('http://SOME_URL/posts')
.then(res => res.json())
}
function post(root, args) {
return getPost(args.id)
}
function post(root, args) {
return getPost(args.id)
.then(post => {
console.log(post)
})
}
function post(root, args) {
return Post.findOne({ where: { id: args.id } }, function (err, post) {
return post
})
其中,Post
是我们通过上下文注入的一些模型。如果我们使用的是sequelize
,我们可以调用findAll
<代码>猫鼬和