【问题标题】:Parallel promise execution in resolve functions解析函数中的并行承诺执行
【发布时间】:2017-09-23 02:36:05
【问题描述】:

我有一个关于在 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()/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() 解析器返回的每个承诺都将并行执行。

我已经能够使用 facebook 的 dataloader 库进一步消除使用相同 userId 重新获取作者的问题。 但是,我想使用自定义缓存而不是 dataloader

问题

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

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

感谢您的任何提示!

【问题讨论】:

  • 未来读者请注意:当我说graphql-js 解析器通过“并行”执行来做任何事情时,我错了。正如 Lee 下面所说,resolve 函数和它们返回的 promise 会急切地执行。在这种情况下,从 Post.author() 解析器返回的所有承诺同时处于“飞行中”,这使得我在上面尝试的缓存检查变得不可能。通过一些唯一键(idurl)来记忆请求是可行的方法,我将使用 DataLoader。

标签: graphql graphql-js apollo


【解决方案1】:

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

GraphQL 和 graphql.js 库本身并不关心加载数据 - 它们通过解析器函数将其留给您。 Graphql.js 只是尽可能急切地调用这些解析器函数,以提供最快的整体查询执行。您绝对可以决定返回按顺序解析的 Promise(我不推荐),或者——正如 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;   
}

【讨论】:

    【解决方案2】:

    仅适用于将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 作为缓存机制。 希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-17
      • 2017-07-01
      • 2021-01-22
      • 2016-01-10
      • 1970-01-01
      • 2021-06-05
      • 2020-12-23
      • 1970-01-01
      相关资源
      最近更新 更多