为什么 graphql-js 执行器在父解析器 return null/undefined 时停止解析具有自己的解析器的子字段?

Why graphql-js executor stops resolving child fields that have their own resolvers when the parent resolver return null/undefined?

在 JavaScript 中为 GraphQL 编写库时,我偶然发现了一个奇怪的行为。我设法在一个非常简单的示例中将其隔离。让我们来看看这个服务器片段:


    const { ApolloServer, gql } = require("apollo-server")

    const typeDefs = gql`
      type Book {
        resolveItSelf: String
      }

      type Query {
        book: Book
      }
    `

    const resolvers = {
      Query: {
        book: () => {
          return null // same behavior with undefined here
        }
      },
      Book: {
        resolveItSelf: () => "resolveItSelf"
      }
    }

    const server = new ApolloServer({ typeDefs, resolvers })

    server.listen().then(({ url }) => {
      console.log(`  Server ready at ${url}`)
    })

如果我们使用以下查询查询此服务器:

    {
      book {
        resolveItSelf   
      }
    }

我们得到这个结果:

{
  "data": {
    "book": null
  }
}

所以,我期待 graphql 执行器尝试解析 "resolveItSelf" 字段(它有自己的解析器),即使书籍解析器 return 为 null。

获得我期望的行为的一种方法是稍微更改本书的解析器:

const resolvers = {
  Query: {
    book: () => {
      return {} // empty object instead of null/undefined
    }
  },
  Book: {
    resolveItSelf: () => "resolveItSelf"
  }
}

然后我们得到这个结果:

{
  "data": {
    "book": {
      "resolveItSelf": "resolveItSelf"
    }
  }
}

即使父级为空,该字段也会被解析!

所以我的问题是为什么 graphql-js 执行程序停止尝试解析父级解析器 return null/undefined 的字段,即使请求的字段可以自行解析? (草案中是否有涵盖此内容的部分?)

在GraphQL中,null表示缺少一个值。如果字段解析为 null,则调用其 "child" 字段解析器是没有意义的,因为无论如何它们都不会在响应中返回。

来自 spec 的价值完成部分(强调我的):

  1. If the fieldType is a Non‐Null type:
    a. Let innerType be the inner type of fieldType.
    b. Let completedResult be the result of calling CompleteValue(innerType, fields, result, variableValues).
    c. If completedResult is null, throw a field error.
    d. Return completedResult.
  2. If result is null (or another internal value similar to null such as undefined or NaN), return null.
  3. If fieldType is a List type:
    a. If result is not a collection of values, throw a field error.
    b. Let innerType be the inner type of fieldType.
    c. Return a list where each list item is the result of calling CompleteValue(innerType, fields, resultItem, variableValues), where resultItem is each item in result.
  4. If fieldType is a Scalar or Enum type:
    a. Return the result of “coercing” result, ensuring it is a legal value of fieldType, otherwise null.
  5. If fieldType is an Object, Interface, or Union type:
    a. If fieldType is an Object type.
    i. Let objectType be fieldType.
    b. Otherwise if fieldType is an Interface or Union type.
    i. Let objectType be ResolveAbstractType(fieldType, result).
    c. Let subSelectionSet be the result of calling MergeSelectionSets(fields).
    d. Return the result of evaluating ExecuteSelectionSet(subSelectionSet, objectType, result, variableValues) normally (allowing for parallelization).

换句话说,即使一个字段的类型是一个对象(因此有一组选择的字段也可以解析),如果它解析为 null,则不会沿着该路径进一步执行。