学习教程
执行

执行

¥Execution

经过验证后,GraphQL 查询由 GraphQL 服务器执行,该服务器返回反映所请求查询的形状的结果,通常为 JSON。

¥After being validated, a GraphQL query is executed by a GraphQL server which returns a result that mirrors the shape of the requested query, typically as JSON.

如果没有类型系统,GraphQL 无法执行查询,让我们使用示例类型系统来说明执行查询。这是这些文章中的示例中使用的同一类型系统的一部分:

¥GraphQL cannot execute a query without a type system, 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 these articles:

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

为了描述执行查询时会发生什么,让我们用一个例子来演练一下。

¥In order to describe what happens when a query is executed, let’s use an example to walk through.

你可以将 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. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the resolver which is provided 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 which apply to that object. This continues until scalar values are reached. GraphQL queries always end at scalar values.

根字段和解析器

¥Root fields & resolvers

每个 GraphQL 服务器的顶层都有一个类型,代表 GraphQL API 的所有可能入口点,通常称为根类型或查询类型。

¥At the top level of every GraphQL server is a type that represents all of the possible entry points into the GraphQL API, it’s often called the Root type or the Query type.

在此示例中,我们的查询类型提供了一个名为 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 object.

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. A resolver function receives four arguments:

  • obj 对于根查询类型上的字段,通常不使用前一个对象。

    ¥obj The previous object, which for a field on the root Query type is often not used.

  • args 提供给 GraphQL 查询中字段的参数。

    ¥args The arguments provided to the field in the GraphQL query.

  • context 提供给每个解析器并保存重要上下文信息(例如当前登录的用户或对数据库的访问权限)的值。

    ¥context A value which is provided to every resolver and holds important contextual information like the currently logged in user, or access to a database.

  • info 保存与当前查询相关的特定于字段的信息以及结构详细信息的值,另请参阅 输入 GraphQLResolveInfo 了解更多详细信息

    ¥info A value which holds field-specific information relevant to the current query as well as the schema details, also refer to type GraphQLResolveInfo for more details.

异步解析器

¥Asynchronous resolvers

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

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

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

context 用于提供对数据库的访问,该数据库用于通过在 GraphQL 查询中作为参数提供的 id 为用户加载数据。由于从数据库加载是异步操作,因此返回 Promise。在 JavaScript 中,Promise 用于处理异步值,但许多语言中都存在相同的概念,通常称为 Future、Task 或 Deferred。当数据库返回时,我们可以构造并返回一个新的 Human 对象。

¥The context is used to provide access to a database which is used to load the data for a user by the id provided as an argument in the GraphQL query. 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, we can construct and return a new Human object.

请注意,虽然解析器函数需要了解 Promises,但 GraphQL 查询不需要。它只是期望 human 字段返回一些内容,然后可以向 name 询问该内容。在执行过程中,GraphQL 将等待 Promises、Futures 和 Task 完成,然后再继续,并且将以最佳并发性执行此操作。

¥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 which it can then ask the name of. During execution, GraphQL will wait for Promises, Futures, and Tasks to complete 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 on it.

Human: {
  name(obj, args, context, info) {
    return obj.name
  }
}

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

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

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

¥Resolving the name in this case is very straight-forward. 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 that Human object to have a name property which we can read and return directly.

事实上,许多 GraphQL 库会让你省略如此简单的解析器,并且只会假设如果没有为字段提供解析器,则应读取并返回同名的属性。

¥In fact, many GraphQL libraries will let you omit resolvers this simple and will just assume that if a resolver isn’t provided for a field, that 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 values with known values, however this function is returning numbers! Indeed if we look up at the result we’ll see that the appropriate Enum values are being returned. What’s going on?

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

¥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 defined on our server which uses numbers like 4, 5, and 6 internally, but represents them as Enum values in the GraphQL type system.

列出解析器

¥List resolvers

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

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

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 go load all of those ids to get real Starship objects.

GraphQL 将在继续之前同时等待所有这些 Promise,当留下对象列表时,它将再次并发地继续加载每个项目上的 name 字段。

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

产生结果

¥Producing the result

解析每个字段时,结果值将放入键值映射中,其中字段名称(或别名)作为键,解析的值作为值。这从查询的底部叶字段一直延续到根查询类型上的原始字段。这些共同产生一个反映原始查询的结构,然后可以将其发送(通常作为 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 all the way 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 which requested it.

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

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