Apollo 服务器使用部分数据引发多个错误

Posted

技术标签:

【中文标题】Apollo 服务器使用部分数据引发多个错误【英文标题】:Apollo server throw multiple errors with partial data 【发布时间】: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 则不可能 掩盖不良数据库设计似乎是人为问题...改为使用数据库一致性 【参考方案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 客户端出现问题。

【讨论】:

谢谢。这个解决方案就是我一直在寻找的***.com/a/41603497/4868839【参考方案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

【讨论】:

谢谢。这个解决方案就是我一直在寻找的***.com/a/41603497/4868839

以上是关于Apollo 服务器使用部分数据引发多个错误的主要内容,如果未能解决你的问题,请参考以下文章

使用 graphql 和 apollo 引发描述性错误

所有 apollo 查询错误都会引发网络 400 错误

使用 apollo 客户端在 react.js 中上传文件

Apollo 服务器多个 3rd 方 API

使用 Apollo 客户端和 React 的多个查询

Apollo GraphQL 错误处理