我建议只使用 Promise API 而不是回调,因为这样可以更轻松地处理错误。另外,在您的代码中,您还将回调和 Promise API 混合在一起,这将导致一些不一致。
不包含您的类别的原因是因为您在 category 字段更新之前返回了 ticket 对象。
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;
});
代码
将其混合到您的代码中并删除一些语法糖以提高性能,结果是:
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];
}
}