我偶尔会在onCreate和onDelete触发器上观察到这种行为。
两个执行都发生在firestore中创建的同一文档中。那里只有一个文档,所以我不明白它是如何触发处理程序两次的。处理程序本身非常简单:
module.exports = functions.firestore.document('notes/{noteId}').onCreate((event) => {
const db = admin.firestore();
const params = event.params;
const data = event.data.data();
// empty
});
这不会一直发生。我错过了什么?
答案 0 :(得分:8)
请参阅Cloud Firestore触发器Limitations and Guarantees:
目前不保证传递函数调用。作为 Cloud Firestore和Cloud Functions集成改进,我们计划 保证“至少一次”交付。但是,这可能并非总是如此 测试期间的情况。 这也可能导致多次调用 对于单个事件,所以对于最高质量的功能确保 这些函数被写成幂等的。
有Firecast video提供实施幂等的提示。
另外两个Google博文:the first,the second。
答案 1 :(得分:1)
在我的情况下,我尝试使用 eventId 和交易来防止onCreate有时会触发两次
(你可能需要在列表中保存eventId,如果你的功能经常被触发,检查它是否存在)
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const db = admin.firestore()
exports = module.exports = functions.firestore.document('...').onCreate((snap, context) => {
const prize = 1000
const eventId = context.eventId
if (!eventId) {
return false
}
// increment money
const p1 = () => {
const ref = db.doc('...')
return db.runTransaction(t => {
return t.get(ref).then(doc => {
let money_total = 0
if (doc.exists) {
const eventIdLast = doc.data().event_id_last
if (eventIdLast === eventId) {
throw 'duplicated event'
}
const m0 = doc.data().money_total
if(m0 !== undefined) {
money_total = m0 + prize
}
} else {
money_total = prize
}
return t.set(ref, {
money_total: money_total,
event_id_last: eventId
}, {merge: true})
})
})
}
// will execute p2 p3 p4 if p1 success
const p2 = () => {
...
}
const p3 = () => {
...
}
const p4 = () => {
...
}
return p1().then(() => {
return Promise.all([p2(), p3(), p4()])
}).catch((error) => {
console.log(error)
})
})
答案 2 :(得分:1)
基于@saranpol的回答,我们现在使用以下内容。我们尚未检查是否确实得到了重复的事件ID。
const alreadyTriggered = eventId => {
// Firestore doesn't support forward slash in ids and the eventId often has it
const validEventId = eventId.replace('/', '')
const firestore = firebase.firestore()
return firestore.runTransaction(async transaction => {
const ref = firestore.doc(`eventIds/${validEventId}`)
const doc = await transaction.get(ref)
if (doc.exists) {
console.error(`Already triggered function for event: ${validEventId}`)
return true
} else {
transaction.set(ref, {})
return false
}
})
}
// Usage
if (await alreadyTriggered(context.eventId)) {
return
}