Я бы рекомендовал работать исключительно с 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];
}
}