Загрузчик данных для кэширования и запроса к базе данных
Как кеширует и запрашивает загрузчик данных и сохраняет их в ключе.
https://github.com/facebook/dataloader
Здесь мне требуется пакет dataloader и создать экземпляр, но как он работает над идеями................................... для запроса базы данных, приведите пример таблицы поиска БД
var DataLoader = require('dataloader')
var userLoader = new DataLoader(keys => myBatchGetUsers(keys));
userLoader.load(1)
.then(user => userLoader.load(user.invitedByID))
.then(invitedBy => console.log(`User 1 was invited by ${invitedBy}`));
// Elsewhere in your application
userLoader.load(2)
.then(user => userLoader.load(user.lastInvitedID))
.then(lastInvited => console.log(`User 2 last invited ${lastInvited}`));
1 ответ
Утилита DataLoader от Facebook работает, комбинируя входные запросы с пакетной функцией, которую вы должны предоставить. Работает только с запросами, которые используют Identifiers
,
Есть три этапа:
- Этап агрегации: любой запрос на
Loader
объект задерживается доprocess.nextTick
- Пакетная фаза:
Loader
просто позвониmyBatchGetUsers
Функция, которую вы предоставили с комбинацией всех запрашиваемых ключей. - Фаза разделения: результат затем "разделяется", поэтому входные запросы получают желаемую часть ответа.
Вот почему в приведенном вами примере у вас должно быть только два запроса:
- Один для пользователей 1 и 2
- Тогда один для связанных пользователей (
invitedByID
)
Например, чтобы реализовать это с помощью mongodb, вам нужно просто определить функцию myBatchGetUsers для использования find
метод соответственно:
function myBatchGetUsers(keys) {
// usersCollection is a promisified mongodb collection
return usersCollection.find(
{
_id: { $in: keys }
}
)
}
Я нашел полезным воссоздать часть dataloader
который я использую, чтобы увидеть один из возможных способов его реализации. (в моем случае я использую только.load()
функция)
Итак, создавая новый экземпляр DataLoader
конструктор дает вам 2 вещи:
- Список идентификаторов (для начала пустой)
- Функция, которая использует этот список идентификаторов для запроса базы данных (вы предоставляете это).
Конструктор может выглядеть примерно так:
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
}
При создании нового объекта через конструктор DataLoader (new DataLoader(fn)
), fn
вы передаете ему, чтобы получить данные откуда-то, принимая массив ключей в качестве аргументов, и вернуть обещание, которое разрешается в массив значений, который соответствует начальному массиву ключей.
Например, вот фиктивная функция, которая принимает массив ключей и передает тот же массив обратно, но с удвоенными значениями:
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
будет вызываться только один раз. На самом деле это очень похоже на противодействие. Или, по крайней мере, это полезно при создании веб-сайтов, и вы хотите что-то делать наmousemove
событие, но вы получаете гораздо больше событий, чем вы хотите иметь дело. Я ДУМАЮ, что это называется противодействие.
Но звонит .load(key)
также нужно нажать клавишу _keys
массив, который мы можем в теле .load
функцию, нажав кнопку key
аргумент _keys
(просто this._keys.push(key)
). Однако договор.load
Функция состоит в том, что она возвращает единственное значение, относящееся к тому, к чему разрешается ключевой аргумент. В какой-то моментbatchLoadingFn
будет вызван и получит результат (он должен вернуть результат, соответствующий _keys
). Кроме того, требуется, чтобыbatchLoadingFn
фактически возвращает обещание этого значения.
Следующий фрагмент, который я подумал, был особенно умным (и стоит усилий взглянуть на исходный код)!
В dataloader
библиотеке, вместо того, чтобы хранить список ключей в _keys
, фактически хранит список ключей, связанных со ссылкой на resolve
функция, которая при вызове приводит к разрешению значения в результате .load()
. .load()
возвращает обещание, обещание разрешается, когда оно 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
с _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
. Но я не мог заставить это работать.