解析函数中的并行承诺执行

时间:2017-04-25 21:46:50

标签: graphql graphql-js apollo

我有一个关于在GraphQL客户端的解析函数中处理promise的问题。传统上,解析器将在服务器上实现,但我在客户端上包装REST API。

背景和动机

给定解析器如:

const resolvers = {
  Query: {
    posts: (obj, args, context) => {
      return fetch('/posts').then(res => res.json());
    }
  },
  Post: {
    author: (obj, args, _, context) => {
      return fetch(`/users/${obj.userId}`)
        .then(res => res.json());
        .then(data => cache.users[data.id] = data)
    }
  }
};

如果我运行查询:

posts {
  author {
    firstName
  }
}

并且Query.posts() /posts API会返回四个帖子对象:

[
  {
    "id": 1,
    "body": "It's a nice prototyping tool",
    "user_id": 1
  },
  {
    "id": 2,
    "body": "I wonder if he used logo?",
    "user_id": 2
  },
  {
    "id": 3,
   "body": "Is it even worth arguing?",
   "user_id": 1
  },
  {
    "id": 4,
    "body": "Is there a form above all forms? I think so.",
    "user_id": 1
  }
]

Post.author()解析器将被调用四次以解析author字段。

grapqhl-js有一个非常好的功能,其中从Post.author()解析器返回的每个promise将并行执行。

  

我还能够使用Facebook的dataloader库,使用相同的userId来取消重新获取作者。 但是,我想使用自定义缓存而不是dataloader

问题

有没有办法阻止Post.author()解析器并行执行?在Post.author()解析器中,我想一次获取一个author,检查其间的缓存以防止重复的http请求。

但是,现在从Post.author()返回的promise已经排队并立即执行,因此我无法在每次请求之前检查缓存。

感谢您的任何提示!

2 个答案:

答案 0 :(得分:0)

我绝对建议您查看DataLoader,因为它旨在解决此问题。如果你不直接使用它,至少你可以阅读它的实现(这不是那么多行)并借用你的自定义缓存上的技术。

GraphQL和graphql.js库本身并不关心加载数据 - 它们通过解析器函数将它们留给您。 Graphql.js只是急切地调用这些解析器函数,以便为查询提供最快的整体执行。你绝对可以决定返回顺序解析的Promises(我不推荐),或者 - 当DataLoader使用memoization实现重复数据删除时(这就是你想要解决的问题)。

例如:

const resolvers = {
  Post: {
    author: (obj, args, _, context) => {
      return fetchAuthor(obj.userId)
    }
  }
};

// Very simple memoization
var authorPromises = {};
function fetchAuthor(id) {
  var author = authorPromises[id];
  if (!author) {
    author = fetch(`/users/${id}`)
      .then(res => res.json());
      .then(data => cache.users[data.id] = data);
    authorPromises[id] = author;
  }
  return author;   
}

答案 1 :(得分:0)

仅对于将dataSourcedataLoader一起用于REST api的人(在这种情况下,它实际上并没有帮助,因为它是一个请求)。这是一个简单的缓存解决方案/示例。

export class RetrievePostAPI extends RESTDataSource {
  constructor() {
    super()
    this.baseURL = 'http://localhost:3000/'
  }
  postLoader = new DataLoader(async ids => {
    return await Promise.all(
      ids.map(async id => {
        if (cache.keys().includes(id)) {
          return cache.get(id)
        } else {
          const postPromise = new Promise((resolve, reject) => {
            resolve(this.get(`posts/${id}`))
            reject('Post Promise Error!')
          })
          cache.put(id, postPromise, 1000 * 60)
          return postPromise
        }
      })
    )
  })

  async getPost(id) {
    return this.postLoader.load(id)
  }
}

注意:这里我使用memory-cache进行缓存。 希望这会有所帮助。