学习教程
执行

执行

¥Execution

Learn how GraphQL provides data for requested fields

解析后的文档为 validated 之后,客户端的请求将由 GraphQL 服务器进行 executed,返回的结果将反映所请求查询的形状。在此页面上,你将了解 GraphQL 操作的执行阶段,其中根据客户端请求的字段从现有源读取数据或将数据写入现有源。

¥After a parsed document is validated, a client’s request will be executed by the GraphQL server and the returned result will mirror the shape of the requested query. On this page, you’ll learn about the execution phase of GraphQL operations where data is read from or written to an existing source depending on what fields are requested by a client.

字段解析器

¥Field resolvers

如果没有类型系统,GraphQL 就无法执行操作,因此让我们使用示例类型系统来说明如何执行查询。这是本指南中所有示例中使用的相同类型系统的一部分:

¥GraphQL cannot execute operations without a type system, so let’s use an example type system to illustrate executing a query. This is a part of the same type system used throughout the examples in this guide:

type Query {
  human(id: ID!): Human
}
 
type Human {
  name: String
  appearsIn: [Episode]
  starships: [Starship]
}
 
enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}
 
type Starship {
  name: String
}

为了描述执行查询时发生的情况,让我们来看一个例子:

¥To describe what happens when a query is executed, let’s walk through an example:

Operation
Response

你可以将 GraphQL 查询中的每个字段视为前一个类型的函数或方法,它返回下一个类型。这正是 GraphQL 的工作方式 - 每种类型的每个字段都由 GraphQL 服务器开发者编写的解析器函数支持。当一个字段被执行时,相应的解析器被调用以产生下一个值。

¥You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. This is exactly how GraphQL works—each field on each type is backed by a resolver function that is written by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.

如果字段生成标量值(如字符串或数字),则执行完成。但是,如果字段生成对象值,则查询将包含适用于该对象的另一组字段。这一直持续到达到叶值。GraphQL 查询始终以标量或枚举类型结束。

¥If a field produces a scalar value like a string or number, then the execution completes. However, if a field produces an object value then the query will contain another selection of fields that apply to that object. This continues until the leaf values are reached. GraphQL queries always end at Scalar or Enum types.

根字段和解析器

¥Root fields and resolvers

每个 GraphQL 服务器的顶层都是一个对象类型,它代表 GraphQL API 的可能入口点,它通常被称为“query 根操作类型”或 Query 类型。如果 API 支持突变以写入数据和订阅以获取实时数据,那么它将具有 MutationSubscription 类型,这些类型公开字段以执行这些类型的操作。你可以在 模式和类型页面 上了解有关这些类型的更多信息。

¥At the top level of every GraphQL server is an Object type that represents the possible entry points into the GraphQL API, it’s often called “the query root operation type” or the Query type. If the API supports mutations to write data and subscriptions to fetch real-time data as well, then it will have Mutation and Subscription types that expose fields to perform these kinds of operations too. You can learn more about these types on the Schema and Types page.

在这个例子中,我们的 Query 类型提供了一个名为 human 的字段,它接受参数 id。此字段的解析器函数可能访问数据库,然后构造并返回 Human 类型:

¥In this example, our Query type provides a field called human which accepts the argument id. The resolver function for this field likely accesses a database and then constructs and returns a Human type:

function Query_human(obj, args, context, info) {
  return context.db.loadHumanByID(args.id).then(
    userData => new Human(userData)
  )
}

此示例是用 JavaScript 编写的,但是 GraphQL 服务器可以在 许多不同的语言 中构建。在参考实现中,解析器函数接收四个参数:

¥This example is written in JavaScript, however GraphQL servers can be built in many different languages. In the reference implementation, a resolver function receives four arguments:

  • obj:上一个对象(对于根 Query 类型上的字段,通常不使用此参数)。

    ¥obj: The previous object (for a field on the root Query type, this argument is often not used).

  • args:GraphQL 操作中提供给字段的参数。

    ¥args: The arguments provided to the field in the GraphQL operation.

  • context:为每个解析器提供的值,可能包含重要的上下文信息,例如当前登录的用户或对数据库的访问。

    ¥context: A value provided to every resolver that may hold important contextual information like the currently logged in user, or access to a database.

  • info:通常仅用于高级用例,这是一个包含与当前操作相关的字段特定信息以及架构详细信息的值;有关更多详细信息,请参阅 类型 GraphQLResolveInfo

    ¥info: generally only used in advanced use-cases, this is a value holding field-specific information relevant to the current operation as well as the schema details; refer to type GraphQLResolveInfo for more details.

请注意,虽然查询操作在执行期间可以在技术上将数据写入底层数据系统,但变异操作通常用于在执行期间产生副作用的请求。并且由于突变操作会产生副作用,因此可以预期 GraphQL 实现会按顺序执行这些字段。

¥Note that while a query operation could technically write data to the underlying data system during its execution, mutation operations are conventionally used for requests that produce side effects during their execution. And because mutation operations produce side effects, GraphQL implementations can be expected to execute these fields serially.

异步解析器

¥Asynchronous resolvers

让我们仔细看看这个解析器函数中发生了什么:

¥Let’s take a closer look at what’s happening in this resolver function:

function Query_human(obj, args, context, info) {
  return context.db.loadHumanByID(args.id).then(
    userData => new Human(userData)
  )
}

GraphQL 查询中的 id 参数指定请求数据的用户,而 context 提供从数据库检索此数据的权限。由于从数据库加载是异步操作,因此返回 Promise。在 JavaScript 中,Promises 用于处理异步值,但许多语言中都存在相同的概念,通常称为 Futures、Tasks 或 Deferred。当数据库返回数据时,我们可以构造并返回一个新的 Human 对象。

¥The id argument in the GraphQL query specifies the user whose data is requested, while context provides access to retrieve this data from a database. Since loading from a database is an asynchronous operation, this returns a Promise. In JavaScript, Promises are used to work with asynchronous values, but the same concept exists in many languages, often called Futures, Tasks, or Deferred. When the database returns the data, we can construct and return a new Human object.

请注意,虽然解析器函数需要了解 Promises,但 GraphQL 查询不需要。它只是期望 human 字段返回可以进一步解析为标量 name 值的内容。在执行期间,GraphQL 将等待 Promises、Futures 和 Tasks 完成后再继续,并以最佳并发性执行。

¥Notice that while the resolver function needs to be aware of Promises, the GraphQL query does not. It simply expects the human field to return something that can be further resolved to a scalar name value. During execution, GraphQL will wait for Promises, Futures, and Tasks to be completed before continuing and will do so with optimal concurrency.

简单的解析器

¥Trivial resolvers

现在 Human 对象可用,GraphQL 执行可以继续处理此类型请求的字段:

¥Now that a Human object is available, GraphQL execution can continue with the fields requested for this type:

function Human_name(obj, args, context, info) {
  return obj.name
}

GraphQL 服务器由类型系统提供支持,该系统用于确定下一步要做什么。甚至在 human 字段返回任何内容之前,GraphQL 就知道下一步将是解析 Human 类型上的字段,因为类型系统告诉它 human 字段将返回此输出类型。

¥A GraphQL server is powered by a type system that is used to determine what to do next. Even before the human field returns anything, GraphQL knows the next step will be to resolve fields on the Human type since the type system tells it that the human field will return this output type.

在这种情况下解析名称很简单。名称解析器函数被调用,obj 参数是从前一个字段返回的新 Human 对象。在这种情况下,我们期望这个对象有一个 name 属性,我们可以直接读取和返回它。

¥Resolving the name in this case is straightforward. The name resolver function is called and the obj argument is the new Human object returned from the previous field. In this case, we expect this object to have a name property, which we can read and return directly.

许多 GraphQL 库允许你省略解析器,假设如果没有为字段提供解析器,则应读取并返回同名的属性。

¥Many GraphQL libraries let you omit resolvers this simple, assuming that if a resolver isn’t provided for a field, a property of the same name should be read and returned.

标量强制

¥Scalar coercion

在解析 name 字段的同时,可以同时解析 appearsInstarships 字段。appearsIn 字段也可以有一个简单的解析器,但让我们仔细看看:

¥While the name field is being resolved, the appearsIn and starships fields can be resolved concurrently. The appearsIn field could also have a trivial resolver, but let’s take a closer look:

Human: {
  appearsIn(obj) {
    return obj.appearsIn // returns [ 4, 5, 6 ]
  }
}

请注意,我们的类型系统声称 appearsIn 将返回具有已知值的枚举类型,但是,此函数返回的是数字!实际上,如果我们查看结果,我们会看到返回了枚举类型的适当值。这是怎么回事?

¥Notice that our type system claims appearsIn will return Enum types with known values, however, this function is returning numbers! Indeed if we look up at the result we’ll see that the appropriate values for the Enum type are being returned. What’s going on?

这是标量强制的一个例子。类型系统知道会发生什么,并将解析器函数返回的值转换为支持 API 契约的值。在这种情况下,我们的服务器上可能定义了一个 Enum 类型,它在内部使用像 456 这样的数字,但将它们表示为 GraphQL 类型系统中的预期值。

¥This is an example of scalar coercion. The type system knows what to expect and will convert the values returned by a resolver function into something that upholds the API contract. In this case, there may be an Enum type defined on our server that uses numbers like 4, 5, and 6 internally, but represents them as the expected values in the GraphQL type system.

列出解析器

¥List resolvers

我们已经看到了当字段返回带有上述 appearsIn 字段的事物列表时发生的一些情况。它返回了一个包含枚举类型值的 列表类型,由于这是类型系统所期望的,因此列表中的每个项目都被强制为适当的值。解析 starships 字段后会发生什么?

¥We’ve already seen some of what happens when a field returns a list of things with the appearsIn field above. It returned a List type containing Enum type values, and since that’s what the type system expected, each item in the list was coerced to the appropriate value. What happens when the starships field is resolved?

function Human_starships (obj, args, context, info) {
  return obj.starshipIDs.map(
    id => context.db.loadStarshipByID(id).then(
      shipData => new Starship(shipData)
    )
  )
}

该字段的解析器不仅仅返回一个 Promise,它还返回一个 Promise 列表。Human 对象有一个他们驾驶的 Starships 的 ID 列表,但我们需要加载所有这些 ID 才能获得真正的 Starship 对象。

¥The resolver for this field is not just returning a Promise, it’s returning a list of Promises. The Human object had a list of IDs of the Starships they piloted, but we need to load all of those IDs to get real Starship objects.

GraphQL 将同时等待所有这些 Promises 然后再继续,并且当剩下对象列表时,它将再次继续同时加载每个项目上的 name 字段。

¥GraphQL will wait for all of these Promises concurrently before continuing, and when left with a list of objects, it will continue yet again to load the name field on each of these items concurrently.

产生结果

¥Producing the result

解析每个字段时,结果值将放入键值映射中,其中字段名称(或别名)作为键,解析的值作为值。这从查询的底部叶字段继续返回到根 Query 类型上的原始字段。这些共同产生一个镜像原始查询的结构,然后可以将其(通常为 JSON)发送给请求它的客户端。

¥As each field is resolved, the resulting value is placed into a key-value map with the field name (or alias) as the key and the resolved value as the value. This continues from the bottom leaf fields of the query back up to the original field on the root Query type. Collectively these produce a structure that mirrors the original query which can then be sent (typically as JSON) to the client that requested it.

让我们最后看一下原始查询,看看所有这些解析函数如何产生结果:

¥Let’s take one last look at the original query to see how all these resolving functions produce a result:

Operation
Response

如我们所见,嵌套选择集中的每个字段在执行查询期间解析为标量叶值。

¥As we can see, each field in the nested selection sets resolves to a scalar leaf value during execution of the query.

后续步骤

¥Next steps

回顾我们学到的关于 GraphQL 响应格式的知识:

¥To recap what we’ve learned about execution:

  • GraphQL 类型系统中的每个字段都将具有相应的解析器函数,该函数从现有数据源为该字段提供数据

    ¥Each field in a GraphQL type system will have a corresponding resolver function that provides data for the field from an existing data source

  • 执行从顶层 QueryMutationSubscription 字段开始

    ¥Execution begins at the top-level Query, Mutation, or Subscription fields

  • 解析器可以异步执行

    ¥Resolvers may execute asynchronously

  • 标量强制将值转换为架构所需的类型

    ¥Scalar coercion converts values into the types expected by the schema

  • 当 Object 类型上的字段返回其他对象的 List 类型时,可能需要从底层数据源获取其他数据,以将任何外键类引用(例如 ID)转换为相关对象

    ¥When a field on an Object type returns a List type of other objects, additional data may need to be fetched from the underlying data source to transform any foreign key-like references (such as IDs) into the related objects

  • 一旦所有请求的字段都已解析为预期的叶值,结果就会发送到客户端,通常为 JSON

    ¥Once all of the requested fields have been resolved to the expected leaf values, the result is sent to the client, typically as JSON

现在我们了解了操作的执行方式,我们可以转到 GraphQL 请求生命周期的最后阶段,其中 response 被传递给客户端。

¥Now that we understand how operations are executed, we can move to the last stage of the lifecycle of a GraphQL request where the response is delivered to a client.