加入我们,于10月8日至10日在纽约市参加GraphQL Federation和API平台工程的最新技巧、趋势和新闻学习。加入我们,参加2024年纽约市GraphQL峰会
文档
免费开始

处理可空性和错误

让您的查询更加类型安全


非空性注释目前在于 Apollo Kotlin 中 experimental 中。如果您对它们有反馈,请通过 GitHub issues, 在 Kotlin Slack 社区, 或者 GraphQL 非空性工作小组 来告诉我们。

介绍

本节是对 GraphQL 中的非空性及其错误的高级描述,这些错误对应用开发者造成的问题。关于提出的解决方案,请直接跳转到 @semanticNonNull 部分。

没有内置 Result 类型。如果一个 出现错误,它将在 JSON 响应中设置为 null,并将错误添加到 errors 数组中。

仅从 Schema 中,无法确定 null 是否为一个有效的业务值或仅针对错误发生:

type User {
id: ID!
# Should the UI deal with user without a name here? It's impossible to tell.
name: String
avatarUrl: String
}

GraphQL的最佳实践建议将默认设置为可空,以处理错误。

来自graphql.org

在GraphQL类型系统中,每个字段默认都是可空的。这是因为许多事情可能会在由数据库和其他服务支持的网络服务中出现错误。

例如,以下在出现错误时得到如下响应:

query GetUser {
user {
id
name
avatarUrl
}
}

...

{
"data": {
"user": {
"id": "1001",
"name": null,
"avatarUrl": "https://example.com/pic.png"
}
},
"errors": [
{
"message": "Cannot resolve user.name",
"path": ["user", "name"]
}
]
}

对于前端开发者来说,这种可空的默认值有一个主要的缺点。它要求你仔细检查UI代码中的每个 字段

有时并不清楚如何处理不同的案例

@Composable
fun User(user: GetUserQuery.User) {
if (user.name != null) {
Text(text = user.name)
} else {
// What to do here?
// Is it an error?
// Is it a true null?
// Should I display a placeholder? an error? hide the view?
}
}

当有很多 字段 时,手动处理每个 null 情况变得非常繁琐。

如果UI能够决定更全局地处理错误,并在任何 字段 失败时显示一个通用错误,那岂不是很好?

提供了nullability 来处理这种情况:

这些工具将GraphQL的默认配置从“处理每个 字段 的错误”更改为“选择您希望处理的错误”。

导入nullability指令

Nullability 指令 是实验性的。您需要使用 @link 指令:

extend schema @link(
url: "https://specs.apollo.dev/nullability/v0.4",
# Note: other directives are needed later on and added here for convenience
import: ["@semanticNonNull", "@semanticNonNullField", "@catch", "CatchTo", "@catchByDefault"]
)

注意

您还需要选择默认的捕获,但更多内容将在稍后介绍

@semanticNonNull

@semanticNonNull 在GraphQL的 类型系统 中引入了一个新类型。

A @semanticNonNull 类型永远不会为 null,除非在 errors 数组中存在错误。

在您的模式中使用它

type User {
id: ID!
# name is never null unless there is an error
name: String @semanticNonNull
# avatarUrl may be null even if there is no error. In that case the UI should be prepared to display a placeholder.
avatarUrl: String
}

注意

@semanticNonNull 是一个 ,因此可以在不破坏当前 GraphQL 工具的情况下引入,但最终目标是引入新的语法。有关详细信息,请参阅 nullability 工作组讨论

对于 fieldsList 类型,@semanticNonNull 仅应用于第一级。如果您需要将其应用于给定的级别,请使用 levels

type User {
# adminRoles may be null if the user is not an admin
# if the user is an admin, adminRoles[i] is never null unless there is also an error
adminRoles: [AdminRole] @semanticNonNull(levels: [1])
}

使用 @semanticNonNull,前端开发人员知道给定的 field 在常规 中永远不会为 null,因此可以根据此采取相应措施。无需再猜测了!

理想情况下,您的 backend 团队使用 @semanticNonNull 指令为其模式进行注释,以便不同的前端团队能够从新的类型信息中受益。

有时这个过程需要时间。

对于这些情况,您可以通过在额外的 文件中使用 @semanticNonNullField 来扩展您的模式:

# Same effect as above but works as a schema extensions
extend type User @semanticNonNullField(name: "name")

您稍后可以与您的 backend 团队分享该文件,并确保您对类型的解释是正确的。

@catch

虽然 @semanticNonNull 是一个描述您数据的 server directive,但 @catch 是一个定义如何处理错误的 client directive

@catch允许以下操作:

  • 将错误处理为 FieldResult<T>,获取访问相邻错误的权限。
  • 抛出错误并让其他父 field处理它或冒泡到 data == null
  • 将错误强制转换为 null(当前 GraphQL 默认设置)。

对于 fieldsList 类型,@catch 仅应用于第一级。如果您需要将其应用于给定的级别,请使用 levels 参数:

query GetUser {
user {
# map friends[i] to FieldResult
friends @catch(to: RESULT, level: 1)
}
}

放置错误

要获取放置错误,请使用 @catch(to: RESULT)

query GetUser {
user {
id
# map name to FieldResult<String> instead of stopping parsing
name @catch(to: RESULT)
}
}

上述查询生成以下 Kotlin 代码:

class User(
val id: String,
// note how String is not nullable. This is because name
// was marked `@semanticNonNull` in the previous section.
val name: FieldResult<String>,
)

使用 getOrNull() 获取值:

println(user.name.getOrNull()) // "Luke Skywalker"
// or you can also decide to throw on error
println(user.name.getOrThrow())

使用 graphQLErrorOrNull() 获取放置的错误:

println(user.name.graphQLErrorOrNull()) // "Cannot resolve user.name"

抛出错误

要抛出错误,请使用 @catch(to: THROW):

query GetUser {
user {
id
# throw any error
name @catch(to: THROW)
}
}

上述查询生成以下 Kotlin 代码:

class User(
val id: String,
val name: String,
)

注意

错误在解析过程中抛出,但在到达您的UI代码之前被捕获。如果没有父字段捕获它,Apollo Kotlin运行时会捕获并把它设置为 ApolloResponse.exception

将错误强制转换为null

要将错误强制转换为 null (当前GraphQL默认值),请使用 @catch(to: NULL):

query GetUser {
user {
id
# coerce errors to null
name @catch(to: NULL)
}
}

上述查询生成以下 Kotlin 代码:

class User(
val id: String,
// Note how name is nullable again despite being marked
// @semanticNonNull in the schema
val name: String?,
)

注意

错误在解析过程中抛出,但在到达您的UI代码之前被捕获。如果没有父字段捕获它,Apollo Kotlin运行时会做并将在 ApolloResponse.exception 中暴露异常。

@catchByDefault

要使用可空性指令,您需要为可空的GraphQL字段选择默认捕获行为,使用 @catchByDefault

您可以选择将可空字段映射到 FieldResult:

# Errors stop the parsing.
extend schema @catchByDefault(to: RESULT)

或抛出错误

# Errors stop the parsing.
extend schema @catchByDefault(to: THROW)

或将错误强制转换为 null,如当前GraphQL默认:

# Coerce errors to null by default.
extend schema @catchByDefault(to: NULL)

(添加 @catchByDefault(to: NULL) 对代码生成器不起作用,但可以使用 @catch 在您的操作 。)

迁移到语义可空性

语义可空性对默认可空的模式最有用。这些都是需要“处理每个字段错误”的模式。

为了将此默认值更改为“选择您想要处理的错误”,您可以使用以下方法

  1. 导入可空性指令。
  2. 默认为转换为null: extend schema @catchByDefault(to: NULL). 这是一个无操作,可以开始探索指令。
  3. 为单个字段添加 @catch,更熟悉它的工作方式。
  4. 当准备好进行大切换时,将 extend schema catch(to: THROW) 更改为(同时)在所有操作/ 上添加 query GetFoo @catch(to: NULL) {}(这是一个无操作)。
  5. 从现在开始,新查询默认使用 catch(to: THROW)
  6. 逐步删除 query GetFoo @catch(to: NULL) {}
上一页
监控网络状态
下一页
实验性WebSocket
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,商业名称为Apollo GraphQL。

隐私政策

公司