嵌套Javascript承诺-从Firestore获取数据

时间:2020-11-12 01:06:31

标签: javascript node.js firebase google-cloud-firestore promise

在过去的3天里,我一直被这个错误困扰,我已经尝试了一切,尝试以1000种方式构建诺言,但似乎没有任何效果。也许我正在失去“全局”,所以希望新的眼睛会有所帮助。感谢您阅读:

我在Firebase Cloud Functions中运行了预定功能。代码试图完成的是

  1. 检查文档是否已过期并将其更改为“无效” >>此部分有效
  2. 如果文档设置为非活动状态,我想查看在Firestore数据库中是否有相同“类型”的其他文档。如果没有其他相同类型的文档,那么我想从我的文档“类型”中删除该类型。

在最近一次尝试中(在下面复制),我检查快照中是否有文档(这意味着存在另一个相同类型的文档,因此不必删除该文档)。然后,如果res!== true,我将删除该文档。

问题是由于某种原因,res永远不会成立。...也许“ res”承诺会在“快照”承诺之前解决?

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)))
                })
            }))
        });
});

3 个答案:

答案 0 :(得分:1)

在这种情况下,我假设res未定义并且评估为假。

在使用.then参数进行res承诺之前,您有一个先前的.then承诺会返回void:

//...
.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数的长度,因此,如果您将return语句简单地放在前面的.then子句中,{{1 }}类似于res

[true, true, true, true, /* ... */]

答案 1 :(得分:1)

要获得所需的结果,您可能需要考虑使用Batched Writes并将代码分成不同的步骤。

一组可能的步骤是:

  1. 获取所有仍处于活动状态的过期文档
  2. 没有过期的文件?记录结果和结束功能。
  3. 对于每个过期的文档:
    • 将其更新为无效
    • 存储类型以供日后检查
  4. 对于要检查的每种类型,请检查是否存在具有该类型的活动文档,如果不存在,请存储该类型以便以后删除。
  5. 没有要删除的类型?记录结果和结束功能。
  6. 删除所有需要删除的类型。
  7. 记录结果和结束功能。

在上述步骤中,步骤3可以使用Batched Writes,而步骤6可以使用arrayRemove() field transform,后者可以删除multiple elements at once以减轻数据库负担。


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(", ")}`);

注意:错误检查已省略,应予以实施。

批量限制

如果您希望达到每批次500个操作的限制,则可以在批次周围添加包装器,以便根据需要自动拆分它们。这里包括一个可能的包装器:

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个操作(读写)。

答案 2 :(得分:0)

snapshot.docs.map((doc)=>{return true})返回类似于[true, false]的数组,而不返回类似于true的布尔值。
因此.then( async (res) => { res===true ? null : await admin.firestore(...无法正常工作。 和

也许您应该进行如下修改。

.then((snapshot) => 
    snapshot.docs.length > 0 ? null : 
        await admin.firestore(...