Re-throw errors on Firebase
TL;DR jump to the conclusion.
Behold 3 Firebase triggers.
The main differences are:
deletemeSwallow
does not re-throw the error in thecatch
block, i.e. it “swallows” the errordeletemeRethrow
re-throws the error in thecatch
blockdeletemeNoCatch
has no catch block, so any errors will propagate up the call-stack naturally
exports.deletemeSwallow = functions.pubsub.topic('deleteme').onPublish((payload) => {
return new Promise(resolve => {
console.log('I am deletemeSwallow')
if (Buffer.from(payload.data, 'base64').toString() === 'fail') {
throw new Error('You want me to fail')
}
resolve(payload)
}).then(payload => {
return console.log('superfluous then block', payload)
}).catch(err => {
console.error('I am just going to swallow the error', err)
// throw err <-- Gulp!
})
})
exports.deletemeRethrow = functions.pubsub.topic('deleteme').onPublish((payload) => {
return new Promise(resolve => {
console.log('I am deletemeRethrow')
if (Buffer.from(payload.data, 'base64').toString() === 'fail') {
throw new Error('You want me to fail')
}
resolve(payload)
}).then(payload => {
return console.log('superfluous then block', payload)
}).catch(err => {
console.error('I am going to rethrow the error', err)
throw err
})
})
exports.deletemeNoCatch = functions.pubsub.topic('deleteme').onPublish((payload) => {
return new Promise(resolve => {
console.log('I am deletemeNoCatch')
if (Buffer.from(payload.data, 'base64').toString() === 'fail') {
throw new Error('You want me to fail')
}
resolve(payload)
}).then(payload => {
return console.log('superfluous then block', payload)
})
})
Happy path
gcloud pubsub topics publish deleteme --message "as you were, soldier"
Given a happy path, this is what we see in the logs - all functions exit with 'OK'
.
Forced failure
gcloud pubsub topics publish deleteme --message "fail"
Given a forced failure, this is what we see in the logs:
deletemeNocatch
exited with'error'
, which is correct considering the error propagated up the call-stack unhindereddeletemeRethrow
exited with'error'
, which is correct considering the error was re-thrown as we’re not recovering from it in thecatch
blockdeletemeSwallow
incorrectly exited with'OK'
because clearly thecatch
block does not recover from the error - it just swallows the error.
Conclusion
If an error happens in your Firebase trigger business logic, and you catch
at any point in your Promise chain, you must either recover from the error, or re-throw the error so that Firebase can exit with the error status.