问题描述
我已经通过查询实现了 graphql apollo 服务器:
ordeRSSearch{
name
orders: {
id
name
}
}
对于不存在数据的订单,我想为具有如下数据的订单抛出未找到错误数据的数组:
{
"errors": [{
"message": "404: Not Found","path": [ "ordeRSSearch","order",1 ],"extensions": { … }
},{
"message": "404: Not Found",3 ],"extensions": { … }
}],"data": “ordeRSSearch”: [
"name": “test”,"orders": [ {
“id": “2”,name: “test”
}]
}
在上面的响应数据中找不到订单 #1 和 #3 和订单#2。
如何在 apollo 服务器中实现这一点。目前,如果我抛出错误,查询执行将停止,并返回一个错误,data
属性为 null
,如下所示:
{
"errors": [{
"message": "404: Not Found","extensions": { … }
}],"data": null
}
解决方法
有两种广泛的解决方案需要考虑,但我们需要更多地了解您的 graphql 架构。
我假设您的架构如下:
type query {
ordersSearch: OrdersSearch!
}
type OrdersSearch {
name: String!
orders: [Order!]!
}
type Order {
id: ID!
name: String!
}
1。对您的架构进行最少的更改
在您的架构中哪些字段被标记为不可为空在这里非常重要。
这是您消息的一部分,让我认为您的架构包含太多“!
”。
如果我抛出一个错误,查询执行就会停止,并且返回一个错误,数据属性为空
根据 GraphQL spec here:
如果出现错误的字段被声明为非空,则空结果将向上冒泡到下一个可空字段。在这种情况下,错误的路径应包括到发生错误的结果字段的完整路径,即使该字段不存在于响应中。
因此,如果您的 Order.name
字段解析器抛出错误,您的 GraphQL 服务器不能只返回空 name
。它既不能返回空 Order
。如果您使用 orders
数组的默认解析器,它不会跳过此空顺序,而 [Order!]!
数组将阻止空元素。所有这些都会冒泡到不能为空的 ordersSearch
,您将得到 data: null
。
那你能做什么?
您可以放宽非空约束。例如这个架构:
type OrdersSearch {
name: String!
orders: [Order]! # Elements can be null.
}
这将允许以下形式的响应。不完全是你想要的,但足够接近。
"data": “ordersSearch”: [
"name": “test”,"orders": [
null,{
“id": “2”,"name": “test”
},null
]
2.使用 graphql 架构重新设计
当预计会发生某些错误时,并且后端和前端开发人员必须就其含义达成一致时,不使用 GraphQL 错误是有意义的。只是让错误成为架构的一部分!然后你可以定义所有的行为。
这样的模式可以:
type query {
ordersSearch: OrdersSearch!
}
type OrdersSearch {
name: String!
orders: [OrderSearchResult!]!
}
type OrderSearchResult {
found: Boolean!
order: Order
errorCode: OrderSearchResultErrorCode
}
type Order {
id: ID!
name: String!
}
enum OrderSearchResultErrorCode {
PRODUCT_OUT_OF_STOCK
PRODUCT_NOT_FOUND
}
,
所以我想我明白你现在在寻找什么。听起来您拥有的对象存在,但您也在进行“一些查找”以查看它是否“完整”或“准备好”或类似的东西。这意味着您有“一些数据”可以返回,并且您想在有部分数据时告诉客户。
TLDR
只要您的解析器返回一个 Promise 数组,而不是单个 Promise,它就会按照您的要求工作。
问题:一个错误
对于这个例子,它只返回一个承诺:
const resolvers = {
Query: {
orders: async () => {
const orderIds = await checkForOrders();
// Wait for all promises to resolve,and then return the single value
const orders = await Promise.all(orderIds.map(async(orderId) => {
return await loadOrder(orderId); // Assume this can throw
}));
return orders;
},},};
响应如下:
{
"errors": [
{
"message": "Order not found: 4adeaca9-81d5-482a-a846-2c09dabfde16","path": ["orders"]
}
],"data": {
"orders": null
}
}
解决方案:Promise 数组
现在,如果我更改解析器,使其返回一组承诺,而不仅仅是一个承诺:
const resolvers = {
Query: {
orders: async () => {
const orderIds = await checkForOrders();
// Return the array of promises
return orderIds.map(async(orderId) => {
return await loadOrder(orderId); // Assume this can throw
});
},};
回复为您提供了您要查找的内容:
{
"errors": [
{
"message": "Order not found: 4adeaca9-81d5-482a-a846-2c09dabfde16","path": ["orders",1]
},{
"message": "Order not found: 8507aaf3-bfc2-46cb-bee2-358847cc632a",2]
}
],"data": {
"orders": [
{
"id": "f18e7627-9844-4927-8441-e61f660b72fd","name": "some name"
},null,{
"id": "8af5089c-fa87-4c37-af0b-ce3b79aea6fc","name": "some other name"
}
]
}
}
编辑:
我意识到这不是完全你所要求的,因为我的回复中有一些 null
值,你的只有成功,但你真的只能得到一个错误一个对象是预期的(并变成 null
)。
如果您真的真的想过滤掉那些 null
值,您可以将 formatResponse
中的一个函数传递给 ApolloServer 并将其删除。请注意,如果您这样做,path
值在语义上将不再为真。在我的例子中,我在数字 1 和 2 上抛出了错误,如果我要去掉 null
值,我的数组将只有 [0,1],而 [1] 将有一个值在其中,因此表示我的条目 1 和 2 错误的 path
字符串可能会导致智能 GraphQL 客户端出现问题。