我发现重新创建dataloader
我使用的部分很有帮助,以查看可以实现它的一种可能方式。(就我而言,我只使用该.load()
功能)
因此,创建DataLoader
构造函数的新实例会为您提供两件事:
- 标识符列表(开头为空)
- 使用此标识符列表来查询数据库的函数(您提供此)。
构造函数可能看起来像这样:
function DataLoader (_batchLoadingFn) {
this._keys = []
this._batchLoadingFn = _batchLoadingFn
}
并且构造函数的实例DataLoader
可以访问一个.load()
函数,该函数需要能够访问该_keys
属性。所以它是在DataLoad.prototype
对象上定义的:
DataLoader.prototype.load = function(key) {
// this._keys references the array defined in the constructor function
}
new DataLoader(fn)
当通过 DataLoader 构造函数 ( )创建一个新对象时,fn
您传递它需要从某个地方获取数据,将一个键数组作为参数,并返回一个解析为对应于初始键数组的值数组的 Promise .
例如,这是一个伪函数,它接受一个键数组,并将相同的数组传回,但值加倍:
const batchLoadingFn = keys => new Promise( resolve => resolve(keys.map(k => k * 2)) )
keys: [1,2,3]
vals: [2,4,6]
keys[0] corresponds to vals[0]
keys[1] corresponds to vals[1]
keys[2] corresponds to vals[2]
然后每次调用该函数时,都会向数组.load(indentifier)
添加一个键,并在某个时候调用该函数,并将该数组作为参数传递。_keys
batchLoadingFn
_keys
诀窍是......如何.load(id)
多次调用但batchLoadingFn
只执行一次?这很酷,也是我探索这个库如何工作的原因。
我发现可以通过指定batchLoadingFn
在超时后执行来执行此操作,但是如果.load()
在超时间隔之前再次调用,则取消超时,添加新键并batchLoadingFn
重新安排调用。在代码中实现这一点如下所示:
DataLoader.prototype.load = function(key) {
clearTimeout(this._timer)
this._timer = setTimeout(() => this.batchLoadingFn(), 0)
}
本质上调用.load()
会删除挂起的对 的调用batchLoadingFn
,然后batchLoadingFn
在事件循环的后面安排一个新的调用。这保证了在很短的时间内如果.load()
被多次调用,batchLoadingFn
只会被调用一次。这实际上与debouncing非常相似。或者,至少在构建网站并且您想在事件中做某事时它很有用mousemove
,但是您得到的事件比您想要处理的要多得多。我认为这被称为去抖动。
但是调用.load(key)
也需要向_keys
数组推送一个键,我们可以在.load
函数体中通过将key
参数推送到_keys
(just this._keys.push(key)
)。但是,该.load
函数的约定是它返回与 key 参数解析的内容有关的单个值。在某些时候,batchLoadingFn
将调用并获得一个结果(它必须返回一个对应于 的结果_keys
)。此外,还需要batchLoadingFn
实际返回该值的承诺。
我认为接下来的这一点特别聪明(非常值得查看源代码)!
该dataloader
库_keys
实际上保留了一个键列表,与对resolve
函数的引用相关联,而不是在 中保留键列表,当调用该函数时,会导致将值解析为.load()
. .load()
返回一个promise,一个promise在它的resolve
函数被调用时被解析。
所以_keys
数组实际上保留了一个[key, resolve]
元组列表。当您batchLoadingFn
返回时,resolve
会使用一个值调用该函数(希望_keys
通过索引号对应于数组中的项目)。
所以.load
函数看起来像这样(就将元组推[key, resolve]
送到_keys
数组而言):
DataLoader.prototype.load = function(key) {
const promisedValue = new Promise ( resolve => this._keys.push({key, resolve}) )
...
return promisedValue
}
剩下的就是执行batchLoadingFn
with_keys
键作为参数,并resolve
在返回时调用正确的函数
this._batchLoadingFn(this._keys.map(k => k.key))
.then(values => {
this._keys.forEach(({resolve}, i) => {
resolve(values[i])
})
this._keys = [] // Reset for the next batch
})
结合起来,实现上述的所有代码都在这里:
function DataLoader (_batchLoadingFn) {
this._keys = []
this._batchLoadingFn = _batchLoadingFn
}
DataLoader.prototype.load = function(key) {
clearTimeout(this._timer)
const promisedValue = new Promise ( resolve => this._keys.push({key, resolve}) )
this._timer = setTimeout(() => {
console.log('You should only see me printed once!')
this._batchLoadingFn(this._keys.map(k => k.key))
.then(values => {
this._keys.forEach(({resolve}, i) => {
resolve(values[i])
})
this._keys = []
})
}, 0)
return promisedValue
}
// Define a batch loading function
const batchLoadingFunction = keys => new Promise( resolve => resolve(keys.map(k => k * 2)) )
// Create a new DataLoader
const loader = new DataLoader(batchLoadingFunction)
// call .load() twice in quick succession
loader.load(1).then(result => console.log('Result with key = 1', result))
loader.load(2).then(result => console.log('Result with key = 2', result))
如果我没记错的话,我认为dataloader
图书馆不使用setTimeout
,而是使用process.nextTick
. 但我无法让它发挥作用。