使用Firebase Functions清理过期数据时如何正确删除Storage关联文件?
解决Firebase定时任务删除过期Firestore记录时关联Storage文件报错的问题
问题背景
我有如下结构的Firestore文档:
startDate - String endDate - String pages - Array(String)
Storage中的文件路径格式为:/pages/fileID
我用Firebase Functions定时任务清理过期数据,删除Firestore记录时想同时删除关联的Storage文件,但控制台报No such object错误,代码如下:
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 })
问题原因及修复方案
- 先删文档再取数据,导致无法获取pages数组:你先执行了
snapshot.ref.delete(),之后再调用snapshot.ref.get()时,文档已经被删除,大概率拿不到有效数据。必须先获取文档的pages数据,再执行文档删除操作。 - forEach不支持异步等待:
albums.forEach(async snapshot => {...})中的异步操作不会被等待,函数会直接返回null,导致文件删除操作还没完成就提前结束。应该用map将每个文档的处理逻辑转为Promise,再用Promise.all等待所有操作完成。 - 文件路径多了冗余斜杠:如果
pages数组里存的路径已经是pages/fileID,那么bucket.file(/${path})会生成/pages/fileID,而Cloud Storage的文件路径不需要开头的斜杠,路径不匹配就会报“找不到对象”的错误。
修复后的完整代码
const functions = require("firebase-functions") const admin = require("firebase-admin") admin.initializeApp({ storageBucket: "..." }) const firestore = admin.firestore() exports.deleteEntries = functions.pubsub.schedule("* * * * *") .onRun(async () => { const bucket = admin.storage().bucket() const today = new Date(Date.now()).toISOString() const albumsRef = firestore.collection('albums') const albumsSnapshot = await albumsRef.where('endDate', '<', today).get() // 把每个文档的处理逻辑转为Promise,统一等待执行 const deletePromises = albumsSnapshot.docs.map(async (doc) => { // 先获取文档数据,确保拿到pages数组 const docData = doc.data() const pages = docData.pages || [] // 生成删除文件的Promise数组,处理路径斜杠问题 const fileDeletePromises = pages.map(path => { const filePath = path.startsWith('/') ? path.slice(1) : path // 捕获文件不存在的错误,避免影响其他操作 return bucket.file(filePath).delete().catch(err => { console.log(`删除文件失败: ${filePath}, 错误: ${err.message}`) }) }) // 等待所有文件删除完成 await Promise.all(fileDeletePromises) // 最后删除Firestore文档 await doc.ref.delete() }) // 等待所有文档的清理流程完成 await Promise.all(deletePromises) console.log('过期数据清理完成') return null })
额外优化说明
- 增加错误捕获机制,单个文件删除失败不会导致整个定时任务崩溃
- 提前将
today转为ISO字符串,避免重复转换操作 - 直接遍历
albumsSnapshot.docs,逻辑更直观 - 调整执行顺序为“先删文件再删文档”,彻底避免文档删除后无法获取路径的问题
内容的提问来源于stack exchange,提问作者Heimdallr
相关产品推荐
相关产品推荐

