firebasegoogle-cloud-platformgoogle-cloud-firestoregoogle-cloud-functionsgoogle-cloud-storage

How to delete files from Storage with Firebase Functions?


I have Firestore database with documents structure like this

startDate - String
endDate - String
pages - Array(String)

and Storage database with files by following path:

/pages/fileID

I run cron job with Firebase Functions to remove stale data. And when I remove records from Firestore I want to remove associated with this record files from Storage also, but get the error: No such object (from Google Cloud console logs). How to do that correctly?

const functions = require("firebase-functions")
const admin = require("firebase-admin")
admin.initializeApp({ storageBucket: "..." })
var firestore = admin.firestore()

exports.deleteEntries = functions.pubsub.schedule("* * * * *")
    .onRun(async () => {
        const bucket = admin.storage().bucket()

        const timeElapsed = Date.now()
        const today = new Date(timeElapsed)

        const albumsRef = firestore.collection('albums')
        const albums = await albumsRef.where('endDate', '<', today.toISOString()).get()
        albums.forEach(async snapshot => {
            snapshot.ref.delete() // This works fine, file removed from Firestore - success
            const promises = []
            await snapshot.ref.get().then(doc => {
                doc.data().pages.forEach(path => { // Error: No such object
                    promises.push(bucket.file(`/${path}`).delete())
                })
            })
            await Promise.all(promises)
        })
        return null
    })

Solution

  • Mixing forEach and async/await is not recommended, see "JavaScript: async/await with forEach()" and "Using async/await with a forEach loop".

    The following code should do the trick (untested):

    exports.deleteEntries = functions.pubsub
      .schedule("* * * * *")
      .onRun(async () => {
        const bucket = admin.storage().bucket();
    
        const timeElapsed = Date.now();
        const today = new Date(timeElapsed);
    
        const albumsRef = firestore.collection("albums");
        const albums = await albumsRef
          .where("endDate", "<", today.toISOString())
          .get();
    
        const promises = [];
    
        albums.forEach((albumSnapshot) => {
          promises.push(albumSnapshot.ref.delete()); 
          albumSnapshot.data().pages.forEach((path) => {
            promises.push(bucket.file(`/${path}`).delete());
          });
        });
    
        await Promise.all(promises);
        return null;
      });
    

    Explanations:

    • albumSnapshot.ref.delete() is an asynchronous operation, so you should add it to the promises Array as well
    • You don't need to do await snapshot.ref.get().then() since the snapshot already contains the document's data. In addition mixing async/await and then() is not recommended neither (even if it works).