Население, связанное с пожарной базой - PullRequest
0 голосов
/ 24 февраля 2020

Я пытаюсь воссоздать приложение Mon goose + Express с Firebase + Express. Но я пытаюсь «заполнить» (например, Mon goose) связанные поля эталонной моделью, но это своего рода вручную, и, возможно, есть более короткий способ добиться этого.

В понедельник goose вы просто кодируете Ticket.find({}).populate('category'), и он автоматически отображает связанный объект. Я не знаю, как этого добиться в Firebase ORM. Мой код выглядит следующим образом, но категория не заполняется должным образом:

export const tickets = (req, res) => {
    // First model reference
    const ref = database.ref('tickets');
    ref.once('value', snapshot => {
        let tickets: object = [];
        if (snapshot.val()) {
            const obj = snapshot.val();
            tickets = Object.keys(obj).map(key => {
                const ticket = { key, ...obj[key] };
                if (ticket.category) {
                    // Related model reference
                    const categoriesRef = database.ref(`categories/${ticket.category}`);
                    categoriesRef.once('value', snapshot => {
                        if (snapshot.val()) {
                            ticket.category = { key: snapshot.key, ...snapshot.val() };
                        }   
                    }).catch(error => res.status(500).json({ error: error.message }));
                }
                return ticket;
            });
        }
        res.json(tickets);
    }).catch(error => res.status(500).json({ error: error.message }));
};

Этот код возвращает все билеты. Они должны включать связанную с ними «категорию» в соответствии с ее ключом. Вы знаете более короткий путь?

1 Ответ

2 голосов
/ 24 февраля 2020

Я бы рекомендовал работать исключительно с API Promise, а не с обратными вызовами, поскольку это облегчит обработку ошибок. Кроме того, в вашем коде вы также смешиваете функции обратного вызова и Promise, что приведет к некоторым несоответствиям.

Причина, по которой ваши категории не включены, заключается в том, что вы возвращаете объект ticket до поле category обновлено.

if (ticket.category) {
    // Related model reference
    const categoriesRef = database.ref(`categories/${ticket.category}`);
    categoriesRef.once('value', snapshot => {
        if (snapshot.val()) {
            ticket.category = { key: snapshot.key, ...snapshot.val() };
        }   
    }).catch(error => res.status(500).json({ error: error.message }));
}
return ticket; // this is evaluated before any of the above asynchronous stuff

Чтобы решить эту проблему, вы должны вернуть Promise с «собранным» билетом.

if (!ticket.category) {
  return Promise.resolve(ticket);
}

return categoriesRef.once('value')
  .then(categorySnapshot => {
    const category = categorySnapshot.val();
    category.key = categorySnapshot.key;
    ticket.category = category;

    return ticket;
  });

Код

Смешивание этого в вашем коде и удаление некоторого сахара syntati c для повышения производительности приводит к:

export const tickets = (req, res) => {
  const categoriesRef = database.ref('categories'); // Changed: moved to start of function
  const ref = database.ref('tickets');

  ref.once('value')
    .then(snapshot => {
      const ticketPromises = []; // array of promises to assembled tickets
      snapshot.forEach(ticketSnapshot => { // NOTE: This is `DataSnapshot#forEach()` not `Array#forEach()`
        // changed: val() creates a fresh object, so we can modify
        //          it without using the spread operator
        const ticket = ticketSnapshot.val(); 
        ticket.key = ticketSnapshot.key;

        if (!ticket.category) {
          // no further assembly required, return the ticket as is
          ticketPromises.push(Promise.resolve(ticket));
          return;
        }

        const ticketPromise = categoriesRef.child(ticket.category).once('value')
          .then(categorySnapshot => {
            // same as before, no need for spread operator
            const category = ticket.category = categorySnapshot.val();
            category.key = categorySnapshot.key;

            return ticket; // return assembled ticket
          });

        ticketPromises.push(ticketPromise);
      });

      return Promise.all(ticketPromises); // wait for all tickets
    })
    .then(tickets => {
      res.json(tickets); // return tickets to client
    })
    .catch(error => {
      console.log(error);
      res.status(500).json({ error: error.message })
    });
};

Коду с кэшированием значений

Поскольку вы также, вероятно, запросите ту же категорию Несколько раз, не изменяя данные, вы также можете кэшировать объекты категории {...data, key} для экономии памяти и минимального времени вычислений, используя следующий код:

export const tickets = (req, res) => {
  const categoryCachedValues = new CachedValues(database.ref('categories'), "key");
  const ref = database.ref('tickets');

  ref.once('value')
    .then(snapshot => {
      const ticketPromises = [];
      snapshot.forEach(ticketSnapshot => { // NOTE: This is `DataSnapshot#forEach()` not `Array#forEach()`
        const ticket = ticketSnapshot.val(); 
        ticket.key = ticketSnapshot.key;

        if (!ticket.category) {
          // no further assembly required, return the ticket as is
          ticketPromises.push(Promise.resolve(ticket));
          return;
        }

        const ticketPromise = categoryCachedValues.get(ticket.category)
          .then(categoryData => {
            ticket.category = categoryData;
            return ticket; // return assembled ticket
          });

        ticketPromises.push(ticketPromise);
      });

      return Promise.all(ticketPromises); // wait for all tickets
    })
    .then(tickets => {
      res.json(tickets); // return tickets to client
    })
    .catch(error => {
      console.log(error);
      res.status(500).json({ error: error.message })
    });
};

class CachedValues {
  constructor(ref, keyFieldName) {
    this._ref = ref;
    this._promises = {};
    if (keyFieldName) {
      this._extractData = (snapshot) => {
        const data = snapshot.val();
        data[keyFieldName] = data.key;
        return data;
      };
    } else {
      this._extractData = (snapshot) => snapshot.val();
    }
  }

  get(path) {
    if (!this._promises[path]) {
      this._promises[path] = this._ref.child(path)
        .once('value')
        .then(this._extractData);
    }
    return this._promises[path];
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...