2
// didn't add node definitions because this problem occurs on initial data fetch

// user type
const userType = new GraphQLObjectType({
  name: 'User',
  fields: () => ({
      id: globalIdField('User'),
      email: { type: GraphQLString },
      posts: {
        type: postConnection,
        args: connectionArgs,
        // getUserPosts() function is below
        resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),
      },
  }),
  interfaces: [nodeInterface],
})

// post type
const postType = new GraphQLObjectType({
  name: 'Post',
  fields: () => ({
    id: globalIdField('Post'),
    title: { type: GraphQLString },
    content: { type: GraphQLString },
  }),
  interfaces: [nodeInterface],
})  

// connection type
const {connectionType: postConnection} =
  connectionDefinitions({name: 'Post', nodeType: postType})

// Mongoose query on other file
exports.getUserPosts = (userid) => {
  return new Promise((resolve, reject) => {
    Post.find({'author': userid}).exec((err, res) => {
      err ? reject(err) : resolve(res)
    })
  })
}

我在浏览器控制台中收到以下警告:

服务器请求查询App失败,原因如下:

  1. 无法读取未定义的属性“长度”

    _posts40BFVD:posts(first:10) {

    ^^^


这是我得到的唯一信息,没有更多的错误或参考。可能是什么原因?

此代码来自relay-starter-kit,我只将所有Widget代码替换为Post. 一切都与启动器中的几乎相同,因此我认为原因是在数据库代码的某个地方。

但我看不到问题,因为getUserPosts()返回相同的结构:对象数组..


出了什么问题?

resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args)

getUserPosts()返回了一个承诺(阻塞代码可能是个坏主意)但没有回调。在我看来发生的事情是connectionFromArray()继续执行代码但它还没有数据getUserPosts(),这导致整个系统失败。

一种可能的解决方案

无论如何,我使用 Babel 和 JavaScript “未来功能”,因此我决定使用asyncand await. 但是还是有问题,getUserPosts()返回了一个空数组。

然后我发现,如果await在一个函数中调用async另一个函数,那么另一个函数也必须async如此,否则都会await-s失败。这是我的最终解决方案:

// async function that makes db query
exports.getUserPosts = async (userId) => {
    try {
      // db query
      const posts = await Post.find({author: args}).exec()
      return posts
    } catch (err) {
      return err
    }
}

// and resolve method in Schema, also async
resolve: async (user, args) => {
  const posts = await getUserPosts(user._id)
  return connectionFromArray(posts, args)
}

我仍然不确定这是否是最好的方法。我的逻辑告诉我应该在 Node 中尽可能多地使用异步,但我远非 Node 专家。当我了解更多信息时,我会更新这个问题。

我很高兴知道是否有更好甚至推荐的方法来使用 Relay 处理这种数据库查询情况。

4

2 回答 2

4

问题在于场的resolve功能posts

resolve: (user, args) => connectionFromArray(getUserPosts(user.id), args),

首先,getUserPosts是一个返回承诺的异步函数。resolve您必须在编写函数时考虑到这一点。其次,user.idgraphql-relay模块的globalIdField辅助函数为您生成的全局 ID 字段。该 ID 应转换为本地 ID。

使用 relay-starter-kit,您可以使用 asyncawait. 代码将如下所示:

resolve: async (user, args) => {
  let userId = fromGlobalId(user.id).id;
  // convert this userId string to Mongo ID if needed
  // userId = mongoose.Types.ObjectId(userId).
  const posts = await getUserPosts(userId);
  return connectionFromArray(posts, args),
},

在您的async版本中getUserPosts,如果出现错误,将返回该err对象。

exports.getUserPosts = async (userId) => {
  try {
    // db query
    const posts = await Post.find({author: args}).exec()
    return posts
  } catch (err) {
    return err
  }
}

一个好的做法是重新抛出错误或返回一个空数组。

于 2016-05-31T19:55:45.060 回答
3

graphql-relay 提供了一个connectionFromPromisedArray函数,该函数等待直到 promise 解决。resolve: (user, args) => connectionFromPromisedArray(getUserPosts(user.id), args),在处理承诺的连接时,这可能是最推荐/最简单的方法。

如果您user.id全局 ID字段,那么您必须从 base64 字符串中提取真实的 DB ID const { type, id } = fromGlobalId(user.id);
或者
resolve: (user, args) => connectionFromPromisedArray(getUserPosts(fromGlobalId(user.id).id), args),

在我使用的使用 Relay 和 Mongoose 的应用程序中,我发现编写自己的连接实现来处理数据库级别而不是应用程序级别的分页和过滤更好。当我写它的时候,我从graffiti-mongoose中获得了很多灵感。

于 2016-06-02T12:38:32.903 回答