原文地址: Execution
在被验证之后,GraphQL 查询将由 GraphQL 服务器执行,该服务器返回的结果反映了请求查询的形状,通常是JSON。
GraphQL 不能在没有类型系统的情况下执行查询,让我们使用一个示例类型系统来演示执行查询。这是在这些文章的例子中使用的相同类型系统的一部分:
type Query {
human(id: ID!): Human
}
type Human {
name: String
appearsIn: [Episode]
starships: [Starship]
}
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Starship {
name: String
}
为了描述在执行查询时发生的情况,我们使用一个例子来说明一下
{
human(id: 1002) {
name
appearsIn
starships {
name
}
}
}
结果
{
"data": {
"human": {
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"starships": [
{
"name": "Millenium Falcon"
},
{
"name": "Imperial shuttle"
}
]
}
}
}
您可以将 GraphQL 查询中的每个字段视为前面类型的函数或方法,返回下一个类型。事实上,这正是GraphQL 的工作方式。每个类型的字段都由一个名为 resolver 的函数来支持,该函数由 graphQL 服务器开发人员提供。当一个字段被执行时,将调用相应的解析器来生成下一个值
如果一个字段产生一个标量值,比如字符串或数字,那么执行就完成了。但是,如果一个字段产生一个对象值,那么查询将包含另一个应用于该对象的字段。这将一直持续到标量值到达为止。GraphQL 查询总是以标量值结束。
根字段 & 解析器 / Root fields & resolvers
在每个 GraphQL 服务器的顶层,都是一个类型,它表示所有可能的入口到 GraphQL API 中,它通常被称为根类型或查询类型。
在本例中,我们的查询类型提供了一个名为 human
的字段,该字段接受参数 id
。该字段的解析器函数很可能访问一个数据库,然后构造并返回一个人工对象。
Query: {
human(obj, args, context) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
}
这个例子是用 JavaScript 编写的,但是 GraphQL 服务器可以用很多不同的语言来编写。解析器函数接收三个参数
-
obj
以前的对象,对于根查询类型的字段,通常不使用。 -
args
在 GraphQL 查询中提供给该字段的参数 -
context
一个为每个解析器提供的值,并保存重要的上下文信息,比如当前登录的用户,或者访问数据库。
匿名解析器 / Asynchronous resolvers
让我们仔细看看这个解析器函数中发生了什么。
human(obj, args, context) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
该 context
用于提供对数据库的访问,该数据库用于通过在 GraphQL 查询中作为参数提供的 id
来为用户加载数据。因为从数据库中加载是异步操作,所以这返回了一个 Promise
。在JavaScript 中,Promise
用于处理异步值,但在许多语言中都存在相同的概念,通常称为Futures
, Tasks
or Deferred
。当数据库返回时,我们可以构造并返回一个新的 Human
对象。
注意,虽然解析器函数需要知道 Promise
,但是 GraphQL 查询没有。它只是希望 human
的字段能够返回一些东西,然后再问它的 name
。在执行期间,GraphQL 将等待 Promise
, Future
, 和 Tasks
在继续之前完成,并在最佳并发性的情况下完成
简单的解析器 / Trivial resolvers
既然已经有了一个 Human
对象,那么 GraphQL 执行就可以继续使用它所请求的字段
Human: {
name(obj, args, context) {
return obj.name
}
}
GraphQL 服务器由一个类型系统驱动,该系统用于确定下一步要做什么。甚至在人类 Human
返回任何东西之前,GraphQL 知道下一步将是解析 Human
类型的字段,因为类型系统告诉它,Human
字段将返回一个人。
在这种情况下,解析名称非常简单。名称解析器函数被调用,obj
参数是来自前一个字段的 new Human
对象。在这种情况下,我们期望 Human
对象拥有一个可以直接读取和返回的 name
属性。
事实上,许多 GraphQL 库会让你忽略这个简单的解析器,并假设如果一个解析器没有为字段提供,那么应该读取和返回相同名称的属性。
标量强制 / Scalar coercion
在解析 name
字段时,可以同时解析 appearsIn
和 starships
字段。appearsIn
也可以有一个简单的解析器,如下:
Human: {
appearsIn(obj) {
return obj.appearsIn // returns [ 4, 5, 6 ]
}
}
注意,我们的类型系统声明将返回带有已知值的枚举值,但是这个函数返回的是数字!事实上,如果我们查看结果,就会看到适当的枚举值被返回。这是怎么运行的呢
这是标量强制的一个例子。类型系统知道预期输出的内容,并将解析器函数返回的值转换为维护 API 约定的值。在本例中,可能在我们的服务器上定义了一个枚举类型,该枚举在内部使用4、5和6等数字,但在 GraphQL 类型系统中表示为枚举值。
列表解析器 / List resolvers
我们已经看到,当一个字段返回了上面出现的 appearsIn
的列表时,会发生什么事情。它返回一个枚举值列表,因为这是类型系统所期望的,所以列表中的每个项都被强制到适当的 enum 值。当 starships
字段获取的时候会发生什么?
Human: {
starships(obj, args, context) {
return obj.starshipIDs.map(
id => context.db.loadStarshipByID(id).then(
shipData => new Starship(shipData)
)
)
}
}
这个字段的解析器不仅仅是返回一个 Promise
,它还在返回一个 Promise 列表。Human
对象有一列他们所引导的 Starships
的 id 列表,但是我们需要加载所有这些 id 来获取真正的 Starship
对象。
GraphQL 将会在继续之前等待所有这些 Promise
,当留下一个对象列表时,它将并发地继续加载每个条目的 name 字段。
组合结果 / Producing the result
每个字段是解析完成后, 由此产生的值被存入一个 key-value
映射的字段名(或别名)作为键和解析值作为值, 继续从底部叶子字段查询备份到根元素的原始字段的查询类型。这些共同生成的结构反映了最初的查询,然后可以将其发送(通常是JSON)到请求它的客户端。
让我们最后看看原始查询,看看这些解析函数是如何产生结果的:
{
human(id: 1002) {
name
appearsIn
starships {
name
}
}
}
查询结果
{
"data": {
"human": {
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"starships": [
{
"name": "Millenium Falcon"
},
{
"name": "Imperial shuttle"
}
]
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。