嵌套Javascript承诺-从firestore获取数据
在过去的3天里,我一直被这个错误困扰着,我尝试了一切,尝试了1000种方式来构建承诺,但似乎没有任何效果。也许我失去了“大局”,所以希望新的眼睛会有所帮助。感谢阅读: 我有一个在Firebase云函数中运行的预定函数。代码试图实现的是嵌套Javascript承诺-从firestore获取数据,javascript,node.js,firebase,google-cloud-firestore,promise,Javascript,Node.js,Firebase,Google Cloud Firestore,Promise,在过去的3天里,我一直被这个错误困扰着,我尝试了一切,尝试了1000种方式来构建承诺,但似乎没有任何效果。也许我失去了“大局”,所以希望新的眼睛会有所帮助。感谢阅读: 我有一个在Firebase云函数中运行的预定函数。代码试图实现的是 检查文档是否过期并将其更改为“非活动”>>此部分有效 如果某个文档被设置为非活动,我想查看firestore数据库中是否有任何其他相同“类型”的文档。如果没有其他相同类型的文档,那么我想从我的文档“类型”中删除该类型 在我最近的尝试(复制如下)中,我检查快照中是否
const functions=require('firebase-functions');
const admin=require('firebase-admin');
admin.initializeApp();
exports.scheduledFunction=functions.pubsub
.时间表('0 23***')。时区('欧洲/马德里')
.onRun(异步(上下文)=>{
const expiredDocs=await admin.firestore()集合('PROMOTIONS\u INFO')
.where('active','=',true)
.where('expiration',“在这种情况下,我假设res
未定义,并且评估为false
在您的。然后
承诺使用res
参数之前,您有一个先前的。然后
承诺返回无效:
//...
.then((snapshot) => {
snapshot.docs.map((doc)=>{return true}) // <--- This is not actually returning a resolved value
}).then(async (res) => {
res===true ? null :
(await admin.firestore().collection('PROMOTIONS_INFO').doc('types')
.update('types', admin.firestore.FieldValue.arrayRemove(type)))
})
//...
snapshot.docs.map((doc)=>{return true})
返回类似于[true,false]的数组
而不是类似于布尔值的true
所以.then(async(res)=>{res==true?null:await admin.firestore(…
无法工作)。
及
也许你应该修改如下
。然后((快照)=>
snapshot.docs.length>0?空:
等待firestore管理员(。。。
<>代码> 为了实现您所期望的结果,您可能需要考虑使用和拆分代码到不同的步骤。
一组可能的步骤是:
获取所有仍处于活动状态的过期文档
没有过期文档?日志结果和结束功能
对于每个过期文件:
- 将其更新为非活动
- 存储它的类型以便稍后检查
对于要检查的每个类型,请检查具有该类型的活动文档是否存在,如果不存在,请存储该类型以稍后删除
没有要删除的类型?日志结果和结束函数
删除所有需要删除的类型
日志结果和结束函数
在上面的步骤中,步骤3可以使用,步骤6可以使用可以移除的来减轻数据库的负担
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(", ")}`);
要使用此功能,请将原始代码中的db.batch()
替换为new MultiBatch(db)
。如果批中的更新(如someBatch.update(ref,{…})
)包含字段转换(如FieldValue.arrayRemove()
),请确保使用someMultiBatch.transformUpdate(ref,{…})
相反,这样一次更新就可以正确地算作两次操作(读和写)。如果要使用()=>{/*code*/}
风格的箭头函数,则需要从所有函数返回
内容。还有一个“末日金字塔”在这里开发承诺和异步/等待意味着消除。考虑是否可以等待<代码> ExpDeCords<代码>集合>结束>代码>。
。然后等待存在集合。然后循环该集合并对每个值运行更新。它起作用了!我非常感谢您的时间和全面的回答。我想,鉴于我们有typesToRemoveArray,也许可以通过读取Types文档,合并t他在代码中添加了两个数组,然后将其写入数据库。这会对多批处理方法产生影响吗?另外,您对运行时间有什么考虑吗?@Mireia您说得很对。今天以全新的眼光重新审视代码时,我记得arrayRemove
一次支持多个元素-当以s单独的参数-可以使用排列(…
)实现运算符。这完全消除了第二个批处理操作。就运行时间而言,上面的代码可能是您能够实现的最精简的代码。最慢的部分将是在有大量数据要检查时检查类型是否仍然存在,但检查每种类型一次有助于保持较低的性能。如果启动t由于更新时间超过60秒而超时,您可以将更新时间增加到9分钟,或者一天运行多次(后者是更好的选择)。只需将0 23***
更改为0 5,11,17,23***
即可每6小时运行一次。这太棒了!谢谢。出于好奇,您知道对多个元素使用arrayRemove是否算作1读写或1像素?
//...
.then((snapshot) => {
return 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)))
})
//...
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(", ")}`);
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()));
}
}