You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 08:57:13