中继连接错误

Connection error with Relay

// 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)
    })
  })
}

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

Server request for query App failed for the following reasons:

  1. Cannot read property 'length' of undefined

    _posts40BFVD:posts(first:10) {

    ^^^


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

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

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


问题是什么?

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

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

一种可能的解决方案

无论如何我都使用 Babel 和 JavaScript "future features",因此我决定使用 asyncawait。但是还是有问题,getUserPosts() 返回了一个空数组。

然后我发现如果另一个函数在async函数中用await调用,那么另一个函数也必须是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 处理这种数据库查询情况。

问题出在 posts 字段的 resolve 函数上。

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

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

使用继电器入门套件,您 can use 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 对象是 returned 以防出错。

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

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

graphql-relay 提供了一个 connectionFromPromisedArray 函数,它会等待 promise 解决。 resolve: (user, args) => connectionFromPromisedArray(getUserPosts(user.id), args), 这可能是处理承诺的连接时最 recommended/easiest 的方式。

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

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