Apollo GraphQL订阅响应不处理嵌套查询

时间:2018-04-12 18:23:55

标签: graphql apollo react-apollo graphql-subscriptions

我有以下GraphQL订阅正常工作

set size square
set xtics nomirror
set ytics nomirror
set x2tic
set y2tic
set x2range [-1:200]
set y2range [-1:204] reverse
set autoscale noextend
set cbrange [0:9]
plot 'ghent0.jpg' binary filetype=jpg with rgbimage, \
     'pher.txt' matrix using 1:2:($3 == 0 ? NaN : $3) axes x2y2 with image notitle

但是以下内容发送了“无法读取属性'用户'未定义”错误

subscription voucherSent($estId: Int!) {
  voucherSent(estId: $estId) {
    id
    name
    usedAt
    sentAt
  }
}

Apollo GraphQL订阅是否处理嵌套查询?

这是我的解析器代码:

subscription voucherSent($estId: Int!) {
  voucherSent(estId: $estId) {
    id
    name
    usedAt
    sentAt
    owner {
      id
      username
    }
  }
}

1 个答案:

答案 0 :(得分:3)

Apollo Graphql订阅包含有关订阅的非常简短的文档。我想我理解您的问题,并且我有完全相同的问题。基于所有的源代码阅读和测试,我认为我对此有一个“不太好的解决方案”。

首先让我解释一下为什么您的代码不起作用。您的代码不起作用是因为用户订阅的用户与进行突变的用户不是同一个人。让我详细说明。 我看到了您的解析器功能,假设该解析器是一些突变解析器,并且在该解析器内部,您执行了pubsub。但是问题是,在该解析器中,您的Web服务器正在处理进行突变的请求。它不知道谁订阅了频道以及他们订阅了哪些字段。因此,最好的选择是发回Voucher模型的所有字段,这就是您所做的

 models.Voucher.findOne({ where: { id: args.id } })

但是它不适用于订阅嵌套字段的订阅者。广播到

时,您绝对可以修改代码
 models.Voucher.include("owner").findOne({ where: { id: args.id } })
 .then(voucher=>pubsub.publish(VOUCHER_SENT, { voucherSent: voucher, estId: voucher.usedIn });

这就像伪代码,但是您知道了。如果您总是使用嵌套字段广播数据,那么您会没事的。但这不是动态的。如果订阅者订阅了更多嵌套字段等,您将会遇到麻烦。

如果您的服务器很简单,并且广播静态数据就足够了。然后,您可以在这里停止。下一节将详细介绍订阅的工作方式。

首先,当客户端进行查询时,您的解析器将传入4个参数。 对于订阅解析器,前3个无关紧要,但后一个包含查询,返回类型等。此参数称为Info。假设您订阅了

subscription {
  voucherSent(estId: 1) {
    id
    name
    usedAt
    sentAt
  }
}

另一个常规查询:

query {
  getVoucher(id:1) {
    id
    name
    usedAt
    sentAt
  }
}

Info参数是相同的,因为它存储了返回类型,返回字段等。取决于查询器的设置方式,如果查询包含嵌套的字段,则应该有某种方式来手动获取结果。

现在,您需要在两个地方编写代码。 1.订阅解析器。在Apollo的文档中,例如:

Subscription: {
  postAdded: {
    // Additional event labels can be passed to asyncIterator creation
    subscribe: () => pubsub.asyncIterator([Channel Name]),
  },
},

在这里,您的订阅是一个函数,其中第四个参数(信息)对于您了解用户已订阅的字段至关重要。因此,您需要以某种方式存储它,如果您有多个用户订阅同一张凭证,但具有不同的字段,则存储它们非常重要。幸运的是,阿波罗graphql-subscription已经做到了。

您的订阅功能应为:

Subscription{
  voucherSent(estid:ID):{
    subscribe: (p,a,c,Info)=>{
        // Return an  asyncIterator object. 
    }
  }
}

要查看为什么它必须是asyncIterator对象,请查看文档here。因此,它有一个很好的帮助器,withFilter函数,它将过滤发布的对象。此函数将一个函数作为第二个参数,这是您决定是否应根据订阅者广播此对象的函数。在示例中,此函数只有2个参数,但是在source code of withFilter中,它实际上有4个参数,第四个是Info,这是您需要的一个参数!

您可能还会注意到,阿波罗的订阅也有一个resolve函数。这意味着,当它向客户端广播有效负载后,您可以在该函数中修改有效负载。

Subscription{
  voucherSent:{
    resolve: (payload, args, context, info)=>{
       // Here the info should tell you that the user also subscribed to  owner field
       // Use payload.value, or id, do model.voucher.include(owner) to construct the nested fields
       // return new payload. 
    },
    subscribe: (p,a,c,Info)=>{
        // Return an  asyncIterator object. 
    }
  }
}

在此设置中,您的订阅至少应该可以运行,但是可能没有优化。因为任何时候都有广播,服务器都可能对每个订户进行数据库查询。每个asyncIterator.next都会调用此解析器。优化它的方法是,您不能依赖asyncIterator并为每个订阅者修改有效负载,您需要首先遍历所有订阅者,知道他们订阅的所有字段的并集。例如,如果用户1

subscribe{voucherSent(id:1){id, name}}

和用户2

subscribe{ voucherSent(id:1){name, sentAt, owner{id,name}}}

您将需要将它们放在一起,并且知道您需要进行一次数据库访问。 假设您要查询

getVoucher(id:1){
  id
  name
  sentAt
  owner{
    id
    name
  }
}

然后将这个联合有效负载发回。这将要求您手动将所有这些订户存储在商店中,并在onConnect和onDisconnect中处理它们。还要弄清楚如何组合这些查询。

希望这会有所帮助,让我知道!

相关问题