【问题标题】:Apollo server throw multiple errors with partial dataApollo 服务器使用部分数据引发多个错误
【发布时间】:2021-07-26 10:45:59
【问题描述】:

我已经使用查询实现了 graphql apollo 服务器:

ordersSearch{
  name
  orders: {
    id
    name
  }
}

对于不存在数据的订单,我想为带有如下数据的订单抛出未找到的错误数组:

   {
    "errors": [{
      "message": "404: Not Found", 
      "path": [ "ordersSearch", 0, "order", 1 ], 
      "extensions": { … }
    }, 
    {
      "message": "404: Not Found", 
      "path": [ "ordersSearch", 0, "order", 3 ], 
      "extensions": { … }
    }],
    "data": “ordersSearch”: [ 
      "name": “test”, 
      "orders": [ {
        “id": “2”,
        name: “test”
    }]
  }

在上述响应数据中未找到订单 #1 和 #3 并找到订单 #2。

如何在 apollo 服务器中实现这一点。目前,如果我抛出错误,查询执行将停止,并返回一个错误,data 属性为null,如下所示:

{
  "errors": [{
    "message": "404: Not Found", 
    "path": [ "ordersSearch", 0, "order", 1 ], 
    "extensions": { … }
  }],
  "data": null
}

【问题讨论】:

  • 也许我正在密切关注这个例子,而不是被问到的原理,但这对我来说很像 REST,而不是 GraphQL。你有什么理由不直接返回null?那是 GraphQL 语言中的“未找到”。
  • 我们想要一种方法让客户发现一些订单没有找到。此外,我们希望捕获并发送部分数据的其他模式道具中可能存在其他错误。
  • 在这种情况下,null 是您告诉客户未找到某些订单的方式orders: [ {...}, null, null, {...}]
  • 我们想让用户知道哪些订单 ID 没有找到。如果我们发送 null 则不可能
  • 掩盖不良数据库设计似乎是人为问题...改为使用数据库一致性

标签: angular error-handling graphql apollo apollo-server


【解决方案1】:

所以我想我明白你现在在寻找什么。听起来您拥有的对象存在,但您也在进行“一些查找”以查看它是否“完成”或“准备好”或类似的东西。这意味着您有“一些数据”可以返回,并且您想在有部分数据时告诉客户。

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",
      "path": ["orders", 2]
    }
  ],
  "data": {
    "orders": [
      {
        "id": "f18e7627-9844-4927-8441-e61f660b72fd",
        "name": "some name"
      },
      null,
      null,
      {
        "id": "8af5089c-fa87-4c37-af0b-ce3b79aea6fc",
        "name": "some other name"
      }
    ]
  }
}

编辑:

我意识到这不是你所要求的完全,因为我的回复中有一些 null 值在那里你只有成功,但你真的只能在一个对象的地方得到一个错误符合预期(并变为null)。

如果你真的真的想要过滤掉那些 null 值,你可以将 formatResponse 中的一个函数传递给 ApolloServer 并将其删除。请注意,如果您这样做,path 值在语义上将不再是正确的。在我的示例中,我在数字 1 和 2 上抛出了错误,如果我要去掉 null 值,我的数组将只有 [0, 1] 和 [1] 中的值,所以path 表示我的条目 1 和 2 不好的字符串可能会导致智能 GraphQL 客户端出现问题。

【讨论】:

【解决方案2】:

有两种广泛的解决方案可供考虑,但我们需要更多地了解您的 graphql 架构。

我假设您的架构如下:

type query {
  ordersSearch: OrdersSearch!
}
type OrdersSearch {
  name: String!
  orders: [Order!]!
}
type Order {
  id: ID!
  name: String!
}

1。对您的架构进行最少的更改

在您的架构中哪些字段被标记为不可为空,这在这里非常重要。 这是您的消息中让我假设您的架构有太多“!”的部分。

如果我抛出错误,查询执行将停止,并返回一个错误,数据属性为 null

根据 GraphQL spec here

如果发生错误的字段被声明为 Non-Null,则 null 结果将冒泡到下一个可为空的字段。在这种情况下,错误的路径应包括发生错误的结果字段的完整路径,即使响应中不存在该字段。

因此,如果您的 Order.name 字段解析器抛出错误,您的 GraphQL 服务器不能只返回空的 name。它也不能返回 null 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
}

【讨论】:

猜你喜欢
  • 2011-10-30
  • 1970-01-01
  • 1970-01-01
  • 2019-03-30
  • 2021-10-25
  • 1970-01-01
  • 2014-07-02
  • 1970-01-01
  • 2020-10-20
相关资源
最近更新 更多