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

Parallel promise execution in resolve functions

我对处理 GraphQL 客户端解析函数中的承诺有疑问。传统上,解析器将在服务器上实现,但我在客户端包装了一个 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()/postsAPIreturns四个post对象:

[
  {
    "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() 解析器返回的每个承诺都将并行执行。

I've further been able to eliminate re-fetching author's with the same userId using facebook's dataloader library. BUT, I'd like to use a custom cache instead of dataloader.

问题

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

但是,现在从 Post.author() 返回的 promise 被排队并立即执行,所以我无法在每个请求之前检查缓存。

感谢您的任何提示!

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

GraphQL 和 graphql.js 库本身不关心加载数据 - 它们通过解析器函数将其留给您。 Graphql.js 只是尽可能急切地调用这些解析器函数,以提供最快的整体查询执行速度。您完全可以决定 return 按顺序解析的 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;   
}

仅适用于一些将 dataSource 用于 REST api 以及 dataLoader 的人(在这种情况下,它并没有真正帮助,因为它是一个单一的请求)。这是一个简单的缓存 solution/example.

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作为缓存机制。 希望这有帮助。