Skip to content

GraphQL - Resolver

解析器(Resolvers)是 GraphQL 服务器执行逻辑的核心。它们是负责获取模式(Schema)中特定字段(Field)数据的函数。当一个 GraphQL 查询(Query)到达时,服务器会逐个遍历查询中的字段,执行每个字段对应的解析器函数来生成最终结果。

可以将模式(Schema)视为定义 API 的结构和类型,而解析器(Resolvers)则提供了实现——实际获取或计算数据的代码。

每个解析器函数接收四个参数:

fieldName(parent, args, context, info) { /* ... return data ... */ }

让我们详细分解这些参数:

参数描述常见用途
parent(或 **root**)父级字段的解析器返回的结果对象。对于 Query 或 Mutation 中的顶层字段,此参数通常是 undefined 或服务器中配置的根值。访问父对象中的数据以解析嵌套字段(例如,从 post 对象获取 userId 以解析 author 字段)。
args一个包含在 GraphQL 查询中传递给字段的参数的对象。如果字段不接受参数,则此对象将为空。基于 ID 获取特定数据(args.id)、过滤列表(args.filter)、为变更(Mutation)提供输入数据(args.input)。
context一个在单个 GraphQL 操作(查询/变更)的所有解析器之间共享的对象。它在每次请求时创建一次,可用于传递请求范围的信息。访问身份验证数据(例如,context.user)、数据库连接或模型(context.db)、用于批量处理的数据加载器(context.loaders)。
info一个包含查询执行状态信息的对象,包括字段名称、返回类型、从根到字段的路径以及查询的抽象语法树(Abstract Syntax Tree, AST)。高级用例,例如实现预读(look-ahead)以根据请求的字段优化数据库查询,或用于复杂的授权逻辑。

通常,您不需要所有这四个参数。一种常见的做法是,对于您不打算使用的参数使用下划线(_)表示,例如 greeting(_, __, context),如果您只需要上下文(Context)的话。

解析器通常组织在一个 JavaScript 对象中(通常称为“解析器映射”,resolver map),其结构镜像了 GraphQL 模式(Schema)。

// Example Schema Snippet
type Query {
greeting: String
students: [Student]
studentById(id: ID!): Student
}
type Student {
id: ID!
firstName: String
lastName: String
# 由特定的 Student 解析器解析的字段
fullName: String
}
// 对应的解析器映射结构
const resolvers = {
Query: {
// Query.greeting 的解析器
greeting: (parent, args, context, info) => { /* 返回问候字符串 */ },
// Query.students 的解析器
students: (parent, args, context, info) => { /* 返回学生列表 */ },
// Query.studentById 的解析器
studentById: (parent, args, context, info) => { /* 根据 args.id 查找并返回学生 */ },
},
Student: {
// Student.fullName 的解析器
// 这会为返回的每个 Student 对象运行
fullName: (parent, args, context, info) => {
// 这里的 'parent' 是单个学生对象
return `${parent.firstName} ${parent.lastName}`;
},
// id, firstName, lastName 通常不需要解析器,如果父对象
// 由 Query 解析器返回的对象已经具有名称匹配的属性。
// GraphQL 为简单的属性访问提供了默认解析器。
},
// ... 如果定义了 Mutation, Subscription 类型,则对应的解析器
};

默认解析器: 如果没有明确提供字段的解析器,GraphQL 通常会使用一个默认解析器。这个默认解析器通常会在 parent 对象上查找同名属性,或调用 parent 对象上同名函数。

解析器可以返回:

  • 标量值(Scalar Values):与模式(Schema)类型匹配的字符串(String)、数字(Number)、布尔值(Boolean)等。
  • 对象(Objects):普通 JavaScript 对象,其属性对应于模式(Schema)中对象类型(Object type)的字段。
  • 数组(Arrays):值或对象的数组,如果模式(Schema)字段类型是列表(List, [])。
  • Promise:解析器通常执行异步操作(例如数据库调用)。返回 Promise 允许 GraphQL 等待操作完成后再继续。
  • Null:如果找不到数据或发生错误(并且字段在模式(Schema)中是可空的)。如果一个不可空字段的解析器返回 null,GraphQL 将触发一个错误。
  • 错误(Errors):抛出错误(尤其是 GraphQLError)表示解析过程中出现问题。

让我们为用于获取学生数据的模式(Schema)创建解析器,包括根据 ID 解析特定学生。

假设有一个基本的 Apollo Server 设置和一个模拟数据源(db.js)。

// db.js(模拟数据 - 示例)
const students = [
{ id: 'S1001', firstName: 'Mohtashim', lastName: 'Mohammad', collegeId: 'col-102' },
{ id: 'S1002', firstName: 'Kannan', lastName: 'Sudhakaran', collegeId: 'col-101' },
];
export const db = {
students: {
list: () => students,
get: (id) => students.find(s => s.id === id),
}
};

步骤 2:定义模式(Schema)(schema.graphql 或在 server.js 中)

Section titled “步骤 2:定义模式(Schema)(schema.graphql 或在 server.js 中)”
#graphql
type Query {
greeting: String
students: [Student] # 返回 Student 对象列表
studentById(id: ID!): Student # 接收一个非空的 ID,返回单个 Student
}
type Student {
id: ID!
firstName: String
lastName: String
collegeId: String # 假设这目前只是数据
}

步骤 3:创建解析器(resolvers.js)

Section titled “步骤 3:创建解析器(resolvers.js)”

实现与模式(Schema)对应的解析器函数。

// resolvers.js
import { db } from './db.js'; // 假设 db.js 使用 export
const resolvers = {
Query: {
// Query.greeting 的解析器
// 这里除了隐式参数外,不需要其他参数
greeting: () => 'Hello from TutorialsPoint!',
// Query.students 的解析器
// 从我们的模拟数据库获取列表
students: () => {
console.log('Resolving: Query.students');
return db.students.list();
},
// Query.studentById 的解析器
// 使用 'args' 参数获取 ID
studentById: (parent, args, context, info) => {
console.log(`Resolving: Query.studentById with id: ${args.id}`);
// 'id' 参数在 args.id 中可用
const student = db.students.get(args.id);
if (!student) {
// 可选:如果找不到学生,则抛出错误,或返回 null
// throw new GraphQLError('未找到学生', { extensions: { code: 'NOT_FOUND' } });
return null; // 如果模式允许 null
}
return student;
},
},
// 这里尚不需要 'Student' 解析器,因为 'id', 'firstName', 'lastName', 'collegeId'
// 直接匹配 Query 解析器返回的对象上的属性。
// GraphQL 的默认解析器会处理这种情况。
};
export default resolvers;

关键点:

  • greeting 解析器仅返回一个静态字符串。
  • students 解析器调用 db.students.list() 来获取数组。
  • studentById 解析器通过 args.id 访问查询中传递的 id,并使用它调用 db.students.get()。它处理了可能找不到学生的情况。
  • 我们不需要为 Student.id、Student.firstName 等定义解析器,因为 students() 和 studentById() 返回的对象已经具有与这些名称完全匹配的属性。在这种情况下,GraphQL 应用默认解析器。

将模式(Schema)和解析器与您的 Apollo Server 设置(server.js)集成,并运行服务器(npm start)。

使用 Apollo Sandbox(http://localhost:9000/graphql)进行测试:

# 测试查询 1:获取问候语和所有学生
query GetGreetingAndStudents {
greeting
students {
id
firstName
}
}
# 测试查询 2:获取特定学生
query GetSpecificStudent {
studentById(id: "S1001") {
id
firstName
lastName
collegeId
}
}
# 测试查询 3:获取不存在的学生
query GetMissingStudent {
studentById(id: "S9999") {
id
firstName
}
}

观察响应以及解析器输出的控制台日志,以查看它们何时执行。

对于嵌套查询,解析器会链式执行。parent 参数连接了这个链。例如,如果您查询 studentById { college { name } },首先会运行 Query.studentById 解析器,返回一个学生对象。然后,会运行 Student.college 解析器,接收该学生对象作为其 parent 参数。