我有一个产品集合,每个产品至少包含一个付款文档。
- products
- productA
- paymentA
- paymentB
- productB
- paymentA
- paymentB
我想在express中公开一个路由来获取嵌套在每个支付文档中的所有产品,就像上面描述的那样。
问题是让Firebase与async-await玩得很好。我试图使用Promise.all()
,但我不能让它在嵌套的情况下工作:
let products = await db
.collection(collections.products)
.where("active", "==", true)
.get()
.then(async (snapshot) => {
let prods = [];
snapshot.forEach(async (doc) => {
let prices = await doc.ref
.collection("prices")
.get()
.then(async (priceSnapshot) => {
let promises = [];
priceSnapshot.forEach(async (priceDoc) => {
promises.push(priceDoc.data());
});
return Promise.all(promises);
});
functions.logger.log("prices", prices);
prods.push({ data: doc, prices });
});
return Promise.all(prods);
});
functions.logger.log("products", products);
res.status(BASIC_HTTP_STATUS_CODES.success).json({ products });
我可以在日志中看到一些价格在产品之后被记录,并且返回的对象是空的。
当我深入一层时,它可以工作,所以如果我只查询产品。
我怎样才能成功地强制程序等待,直到所有的产品文档完成附加相应的付款文档?
问题出在这一行:
Promise.all(prods);
这里,prods
是一个({ data: QueryDocumentSnapshot, prices: DocumentData[] })[]
,而不是一个promise数组。这将导致不一致的结果,这取决于您给Promise.all()
喂什么。
相反,您希望prods
成为Promise<{ data: QueryDocumentSnapshot, prices: DocumentData[] }>[]
。
当你想并行化你的代码时,我会反对使用async
/await
,因为它会导致奇怪的副作用,而且通常情况下,甚至不需要。
let products = await db
.collection(collections.products)
.where("active", "==", true)
.get()
.then((activeProductsQSnap) => { // QSnap = QuerySnapshot
// somewhere to store the promises
const fetchProductAndPricesPromises = [];
// for each product, fetch it's prices
activeProductsQSnap.forEach((productDoc) => {
// here, you want to fetch the prices, wait for the result,
// and then bundle it with productDoc's data. We want to do
// this all in one promise that we can add to the queue.
const thisProductPromise = productDoc.ref
.collection("prices")
.get()
.then((productPricesQSnap) => {
// all this code is linear, no need for async/await
const prices = [];
productPricesQSnap.forEach((priceDoc) => {
prices.push(priceDoc.data());
});
functions.logger.log("prices for product #" + productDoc.id, prices);
return { data: productDoc.data(), prices };
});
// add to queue
fetchProductAndPricesPromises.push(thisProductPromise);
});
// wait for all promises
return Promise.all(fetchProductAndPricesPromises);
});
// if successful, products will have the type: ({ data: DocumentData, prices: DocumentData[] })[]
functions.logger.log("products", products);
res.status(BASIC_HTTP_STATUS_CODES.success).json({ products });
下面是相同的代码,重写为使用async
/await
语法:
async function getPricesForProductDoc(productDoc) {
const productPricesQSnap = await productDoc.ref
.collection("prices")
.get();
const prices = [];
productPricesQSnap.forEach((priceDoc) => {
prices.push(priceDoc.data());
});
functions.logger.log("prices for product #" + productDoc.id, prices);
return { data: productDoc.data(), prices };
}
/* ... */
const activeProductsQSnap = await db
.collection(collections.products)
.where("active", "==", true)
.get();
const promises = [];
activeProductsQSnap.forEach(
(productDoc) => promises.push(getPricesForProductDoc(productDoc))
);
const products = await Promise.all(promises);
functions.logger.log("products", products);
res.status(BASIC_HTTP_STATUS_CODES.success).json({ products });