Загрузчик данных для кэширования и запроса к базе данных

Как кеширует и запрашивает загрузчик данных и сохраняет их в ключе.

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,

Есть три этапа:

  1. Этап агрегации: любой запрос на Loader объект задерживается до process.nextTick
  2. Пакетная фаза: Loader просто позвони myBatchGetUsers Функция, которую вы предоставили с комбинацией всех запрашиваемых ключей.
  3. Фаза разделения: результат затем "разделяется", поэтому входные запросы получают желаемую часть ответа.

Вот почему в приведенном вами примере у вас должно быть только два запроса:

  • Один для пользователей 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 вещи:

  1. Список идентификаторов (для начала пустой)
  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. Но я не мог заставить это работать.

Другие вопросы по тегам