嵌套Javascript承诺-从Firestore获取数据 [英] Nested Javascript promises - fetching data from firestore
问题描述
在过去的3天里,我一直被这个错误困扰,我已经尝试了一切,尝试以1000种方式构建诺言,但似乎没有任何效果.也许我正在失去大局"因此希望新的眼睛会有所帮助.感谢您阅读:
I have been stuck with this error for the past 3 days and i have tried literally everything, tried to structure the promises in a 1000 ways but nothing seems to work. Maybe I am losing the "big picture" so hopefully new eyes will help. Thanks for reading:
我在Firebase Cloud Functions中运行了预定功能.代码试图完成的是
I have a scheduled function running in Firebase Cloud Functions. What the code tries to accomplish is
- 检查文档是否过期并将其更改为非活动".>>这部分有效
- 如果文档被设置为非活动状态,我想查看我在firestore数据库中是否具有相同类型"的其他文档.如果没有其他相同类型的文档,那么我想从我的文档类型"中删除该类型.
在最近一次尝试中(在下面复制),我检查快照中是否有文档(这意味着存在另一个相同类型的文档,因此不必删除该文档).然后,如果res!== true,我将删除该文档.
In my latest attempt (copied below) I check if there is a document in the snapshot (which would mean that there is another document of the same type, therefore the doc doesnt have to be deleted). Then if res!== true, I would delete the document.
问题是由于某种原因,res永远都不是真的.承诺在快照"之前解决答应?
The problem is that for some reason, res is never true.... maybe the "res" promise resolves before the "snapshot" promise?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.scheduledFunction = functions.pubsub
.schedule('0 23 * * *').timeZone('Europe/Madrid')
.onRun( async (context) => {
const expiredDocs = await admin.firestore().collection('PROMOTIONS_INFO')
.where('active','==',true)
.where('expiration', '<=', new Date())
.get()
.then(async (snapshot) => {
await Promise.all(snapshot.docs.map( async (doc) => {
doc.ref.update({active: false})
const type = doc.data().business.type
const id = doc.data().id
const exists = await admin.firestore().collection('PROMOTIONS_INFO')
.where('active','==',true)
.where('business.type','==', type)
.where('id', '!=', id)
.limit(1)
.get()
.then((snapshot) => {
snapshot.docs.map((doc)=>{return true})
}).then(async (res) => {
res===true ? null :
(await admin.firestore().collection('PROMOTIONS_INFO').doc('types')
.update('types', admin.firestore.FieldValue.arrayRemove(type)))
})
}))
});
});
推荐答案
To achieve your desired result, you might want to consider making use of Batched Writes and splitting your code into distinct steps.
一组可能的步骤是:
- 获取所有仍处于活动状态的过期文档
- 没有过期的文件?记录结果结束功能.
- 对于每个过期的文档:
- 将其更新为无效
- 存储类型以供日后检查
在上述步骤中,第3步可以使用批量写入和第6步可以使用 arrayRemove()
字段转换可以删除
In the above steps, step 3 can make use of Batched Writes and step 6 can make use of the arrayRemove()
field transform which can remove multiple elements at once to ease the burden on your database.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.scheduledFunction = functions.pubsub
.schedule('0 23 * * *').timeZone('Europe/Madrid')
.onRun( async (context) => {
// get instance of Firestore to use below
const db = admin.firestore();
// this is reused often, so initialize it once.
const promotionsInfoColRef = db.collection('PROMOTIONS_INFO');
// find all documents that are active and have expired.
const expiredDocsQuerySnapshot = await promotionsInfoColRef
.where('active','==',true)
.where('expiration', '<=', new Date())
.get();
if (expiredDocsQuerySnapshot.empty) {
// no expired documents, log the result
console.log(`No documents have expired recently.`);
return; // done
}
// initialize an object to store all the types to be checked
// this helps ensure each type is checked only once
const typesToCheckObj = {};
// initialize a batched write to make changes all at once, rather than call out to Firestore multiple times
// note: batches are limited to 500 read/write operations in a single batch
const makeDocsInactiveBatch = db.batch();
// for each snapshot, add their type to typesToCheckObj and update them to inactive
expiredDocsQuerySnapshot.forEach(doc => {
const type = doc.get("business.type"); // rather than use data(), parse only the property you need.
typesToCheckObj[type] = true; // add this type to the ones to check
makeDocsInactiveBatch.update(doc.ref, { active: false }); // add the "update to inactive" operation to the batch
});
// update database for all the now inactive documents all at once.
// we update these documents first, so that the type check are done against actual "active" documents.
await makeDocsInactiveBatch.commit();
// this is a unique array of the types encountered above
// this can now be used to check each type ONCE, instead of multiple times
const typesToCheckArray = Object.keys(typesToCheckObj);
// check each type and return types that have no active promotions
const typesToRemoveArray = (await Promise.all(
typesToCheckArray.map((type) => {
return promotionsInfoColRef
.where('active','==',true)
.where('business.type','==', type)
.limit(1)
.get()
.then((querySnapshot) => querySnapshot.empty ? type : null) // if empty, include the type for removal
})
))
.filter((type) => type !== null); // filter out the null values that represent types that don't need removal
// typesToRemoveArray is now a unique list of strings, containing each type that needs to be removed
if (typesToRemoveArray.length == 0) {
// no types need removing, log the result
console.log(`Updated ${expiredDocsQuerySnapshot.size} expired documents to "inactive" and none of the ${typesToCheckArray.length} unique types encountered needed to be removed.`);
return; // done
}
// get the types document reference
const typesDocRef = promotionsInfoColRef.doc('types');
// use the arrayRemove field transform to remove all the given types at once
await typesDocRef.update({types: admin.firestore.FieldValue.arrayRemove(...typesToRemoveArray) });
// log the result
console.log(`Updated ${expiredDocsQuerySnapshot.size} expired documents to "inactive" and ${typesToRemoveArray.length}/${typesToCheckArray.length} unique types encountered needed to be removed.\n\nThe types removed: ${typesToRemoveArray.sort().join(", ")}`);
注意:错误检查已省略,应予以实施.
Note: Error checking is omitted and should be implemented.
如果您希望达到每批次500个操作的限制,则可以在批次周围添加包装器,以便根据需要自动拆分它们.这里包括一个可能的包装器:
If you ever expect to hit the 500 operations per batch limit, you can add a wrapper around batches so they automatically get split up as required. One possible wrapper is included here:
class MultiBatch {
constructor(dbRef) {
this.dbRef = dbRef;
this.batchOperations = [];
this.batches = [this.dbRef.batch()];
this.currentBatch = this.batches[0];
this.currentBatchOpCount = 0;
this.committed = false;
}
/** Used when for basic update operations */
update(ref, changesObj) {
if (this.committed) throw new Error('MultiBatch already committed.');
if (this.currentBatchOpCount + 1 > 500) {
// operation limit exceeded, start a new batch
this.currentBatch = this.dbRef.batch();
this.currentBatchOpCount = 0;
this.batches.push(this.currentBatch);
}
this.currentBatch.update(ref, changesObj);
this.currentBatchOpCount++;
}
/** Used when an update contains serverTimestamp, arrayUnion, arrayRemove, increment or decrement (which all need to be counted as 2 operations) */
transformUpdate(ref, changesObj) {
if (this.committed) throw new Error('MultiBatch already committed.');
if (this.currentBatchOpCount + 2 > 500) {
// operation limit exceeded, start a new batch
this.currentBatch = this.dbRef.batch();
this.currentBatchOpCount = 0;
this.batches.push(this.currentBatch);
}
this.currentBatch.update(ref, changesObj);
this.currentBatchOpCount += 2;
}
commit() {
this.committed = true;
return Promise.all(this.batches.map(batch => batch.commit()));
}
}
要使用此功能,请将原始代码中的 db.batch()
换成 new MultiBatch(db)
.如果批处理中的更新(例如 someBatch.update(ref,{...})
)包含字段转换(例如 FieldValue.arrayRemove()
),请进行请确保改用 someMultiBatch.transformUpdate(ref,{...})
,以便将单个更新正确地计为2个操作(读和写).
To use this, swap out db.batch()
in the original code for new MultiBatch(db)
. If an update in the batch (like someBatch.update(ref, { ... })
) contains a field transform (such as FieldValue.arrayRemove()
), make sure to use someMultiBatch.transformUpdate(ref, { ... })
instead so that single update is correctly counted as 2 operations (a read and a write).
这篇关于嵌套Javascript承诺-从Firestore获取数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!