首页 > 解决方案 > 在 GraphQL 参考实现中,resolvers 期望返回一个 Iterable,如何返回一个异步 Iterable?

问题描述

Sequelize用来访问我的关系数据库并在GraphQL解析器中提供结果。Sequelize框架内的查询是异步执行的( bluebird)。为了缓冲大型结果集并避免在请求数百万条记录时对服务器的高内存需求,我想在我的解析器中返回一个迭代器。考虑这个简化的要点:

// root resolver
function allPersons(...) {
  [...]
  return {
    nextId: 1,
    maxId: 10000000, 
    [Symbol.iterator]: () => { return this },
    next: function() {
      let nextRes = { done: true, value: null }
      if (this.nextId <= this.maxId) {
        nextRes.value = sequelize.models.person.findById(this.currId)
        nextRes.done = false
        this.nextId = this.nextId + 1
      }
      return nextRes
    }
}

上述工作,因为 Sequelize 构造的 Promise 返回为next()'s value。当 this value-Promise 被解析时,它会从底层关系数据库中获取一条记录。因此,我同步构造了异步数据获取。这只有效,因为每个单独的提取都独立于所有其他提取。特别是不需要单次提取awaited,在执行下一个之前。但是,逐行获取关系数据库在技术上效率低下,实际上是一种反模式。因此,我想实现一个缓冲区,它可以获取 10k 行的批次,为它们提供服务,直到批次为空,然后再获取下一个。但是,由于随后引入了异步事件的依赖关系,为了实现这一点,需要一个异步 Iterator ( Symbol.asyncIterator)。

我需要做什么才能使GraphQL's reference implementation(graphql-js 和/或 express-graphql)接受异步迭代器?请注意,我想避免使用Apollo GraphQL

或者 Object-Stream 会是一个可能的解决方案吗?

帮助将不胜感激。

标签: node.jsasynchronousgraphqlgraphql-js

解决方案


半个解决方案:使用流并将它们转换为同步迭代器

因为 GraphQL 解析器预计会返回同步迭代器,所以流可用于将它们的数据提供给这样的迭代器。考虑问题中发布的原始示例的以下解决方案。请注意,流行的 ORM Sequelize不支持流,因此knex这里使用了另一个节点包。

// Setup:
const knex = require('knex')
var dbCon = knex({
  client: 'pg',
  connection: {} // Define host, user, password, db (see knex docu)
})

// Get records as stream
var peopleStream = dbCon.select('*').from('people').stream()

// Serve stream within an synchronous iterator
var iter = {
  [Symbol.iterator]: () => {
    return this
  },
  next: function() {
    let v = peopleStream.read() || null
    console.log(JSON.stringify(v)) // Check, if it works.
    return {
      done: v === null,
      value: v
    }
  }
} 

然而,这确实只是解决方案的一半,因为只有数据源才能以所示的方式使用来生成流——这反过来又可以很容易地转换为同步迭代器,如此处所示。在我看来,GraphQL 的参考实现迫切需要支持异步迭代器作为解析器的结果值。有关更多详细信息,请参阅此功能请求


推荐阅读