Firebase callable functions with background tasks [duplicate] - firebase

I'm using Firebase Functions with https triggers, and I was wondering how long after sending the response to the client, the functions keeps executing. I want to send a response to the client and then perform another operation (send a mail).
Currently I'm doing this as following:
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
doSomeAsyncJob()
.then(() => {
res.send("Ok");
})
.then(() => {
emailSender.sendEmail();
})
.catch(...);
});
The above code is working for me, but I'm suspecting that the code only works because sending the mail has finished before the res.send has completed, so I was wondering how exactly the termination process is working to make sure the code will not break.

You should expect that the HTTP function terminates the moment after you send the response. Any other behavior is some combination of luck or a race condition. Don't write code that depends on luck.
If you need to send a response to the client before the work is fully complete, you will need to kick off a second function to continue where the HTTP function left off. It's common to use a pub/sub function to do with. Have the HTTP function send a pub/sub message to another function, then terminate the HTTP function (by sending a response) only after the message is sent.

If the expected response is not pegged to the outcome of the execution, then you can use
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
res.write('SUCCESS')
return doSomeAsyncJob()
.then(() => {
emailSender.sendEmail();
})
.then(() => {
res.end();
})
.catch(...);
});
This sends back a response a soon as a request is received, but keeps the function running until res.end() is called
Your client can end the connection as soon as a response is received back, but the cloud function will keep running in the background
Not sure if this helps, but it might be a workaround where the client needs a response within a very limited time, considering that executing pub/sub requires some extra processing on its own and takes time to execute

TL;DR
While https functions will terminate shortly after res.send(), it is not guaranteed that 0 lines of code after res.send() will be executed.
I think a fuller answer has 2 components:
as Doug pointed out, do not put any additional code you expect to be executed after res.send()
cloud functions will terminate shortly after res.send(), but don't expect that exactly 0 lines of code will be executed
I ran into a situation where for a db maintenance script, if no records met my criteria, I said goodbye with res.send() and had additional logic after it. I was expecting that piece not to be run, since I've already terminated the request.
Example producing unexpected results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
})
In the above example, I was expecting res.send() to terminate the function immediately - this is not so, the second console.log may also be hit - along with any other code you may have. This is not guaranteed, however, so execution may abruptly stop at some point.
Example producing correct results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
else {
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
}
})
In this version, you will see exactly 1 line of console.logs - as I was originally intending.

Related

Ways to test if my app is making the correct amount of api calls

I'm testing a react app with cypress, and cypress is really good at checking if atleast n many are calls are made on load, and not so good at checking if only those calls were made. The app itself is a pretty standard react app that uses window.fetch for api calls. Is there another service, like cypress something similar, that's got the ability to test that in an automated fashion?
For instance, if you visit the site logged in we have three config calls. If a developer commits code that accidentally calls one of those twice, ideally I'd like an automated test to catch that.
I don't want to do it in Cypress because it seems like that's not what the product is really intended to do unless you hack it with an undocumented solution.
Seems like you need a hard wait to ensure you check after all calls have been issued.
Also recommend stubbing so that there's no latency waiting for server response.
it('check for excess calls', () => {
cy.intercept('https://jsonplaceholder.typicode.com/todos/1', {}).as('fetch')
cy.visit('/').then(() => {
// wait for expected calls
cy.wait('#fetch')
cy.wait('#fetch')
cy.wait('#fetch')
cy.wait(500)
cy.get('#fetch.all') // check the call count
.should(calls => {
expect(calls).to.have.length(3) // no extra calls
})
})
})
Tested with this app HTML
<script>
fetch('https://jsonplaceholder.typicode.com/todos/1')
fetch('https://jsonplaceholder.typicode.com/todos/1')
fetch('https://jsonplaceholder.typicode.com/todos/1')
</script>
You may want to burn-test to get the right wait time, see Burning Tests with cypress-grep
Alternatively you can wait four times and catch the error
it('check for excess calls', () => {
cy.intercept('https://jsonplaceholder.typicode.com/todos/1', {}).as('fetch')
cy.visit('html/call-limit.html').then(() => {
cy.wait('#fetch')
cy.wait('#fetch')
cy.wait('#fetch')
Cypress.once('fail', (error) => {
// catch ignore failure on 4th cy.wait('#fetch')
if (error.message.includes('No request ever occurred')) {
return
}
throw error // rethrow to fail for another reason
})
cy.wait('#fetch') // should fail and trigger listener above
cy.get('#fetch.all')
.should(calls => {
expect(calls).to.have.length(3)
})
})
})

Is it Ok to call cy.server() twice in same test in Cypress

it('Some Test', () => {
cy.server();
cy.route('POST', 'my/api1').as('myApi');
...
cy.wait('#api1');
cy.server();
cy.route('POST', 'my/api2').as('myApi');
...
cy.wait('#api2');
}
Is this code ok, like is there any problem if we are calling cy.server() twice in the same test?in Cypress
I did code as above to see if there is any repercussion, however, couldn't get any!
Basically I wanted to abstract it away in a function like this
Cypress.Commands.add('listenRoute', (type, url, alias) => {
cy.server();
cy.route({
method: type,
url,
}).as(alias);
});
So I got tests successfully passing without any errors or warning, and thus am concluding, that it is OK
The second cy.server(); call is not required as you are waiting cy.wait('#myApi'); for the previous call to complete. Also, the second route is the same as the first, therefore no need to set again if server() is not re-instantiated.
Doco:
Outstanding requests are automatically aborted between tests
Therefore you need to either wait for previous to complete, or initiate a second server to run concurrently.
it('Some Test', () => {
cy.server();
cy.route('POST', 'my/api').as('myApi');
...
cy.wait('#myApi');
cy.wait('#myApi');
cy.visit('#myApi');
cy.wait('#myApi');
cy.visit('#myApi');
}

Firebase function return value while async action is ongoing [duplicate]

I'm using Firebase Functions with https triggers, and I was wondering how long after sending the response to the client, the functions keeps executing. I want to send a response to the client and then perform another operation (send a mail).
Currently I'm doing this as following:
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
doSomeAsyncJob()
.then(() => {
res.send("Ok");
})
.then(() => {
emailSender.sendEmail();
})
.catch(...);
});
The above code is working for me, but I'm suspecting that the code only works because sending the mail has finished before the res.send has completed, so I was wondering how exactly the termination process is working to make sure the code will not break.
You should expect that the HTTP function terminates the moment after you send the response. Any other behavior is some combination of luck or a race condition. Don't write code that depends on luck.
If you need to send a response to the client before the work is fully complete, you will need to kick off a second function to continue where the HTTP function left off. It's common to use a pub/sub function to do with. Have the HTTP function send a pub/sub message to another function, then terminate the HTTP function (by sending a response) only after the message is sent.
If the expected response is not pegged to the outcome of the execution, then you can use
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
res.write('SUCCESS')
return doSomeAsyncJob()
.then(() => {
emailSender.sendEmail();
})
.then(() => {
res.end();
})
.catch(...);
});
This sends back a response a soon as a request is received, but keeps the function running until res.end() is called
Your client can end the connection as soon as a response is received back, but the cloud function will keep running in the background
Not sure if this helps, but it might be a workaround where the client needs a response within a very limited time, considering that executing pub/sub requires some extra processing on its own and takes time to execute
TL;DR
While https functions will terminate shortly after res.send(), it is not guaranteed that 0 lines of code after res.send() will be executed.
I think a fuller answer has 2 components:
as Doug pointed out, do not put any additional code you expect to be executed after res.send()
cloud functions will terminate shortly after res.send(), but don't expect that exactly 0 lines of code will be executed
I ran into a situation where for a db maintenance script, if no records met my criteria, I said goodbye with res.send() and had additional logic after it. I was expecting that piece not to be run, since I've already terminated the request.
Example producing unexpected results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
})
In the above example, I was expecting res.send() to terminate the function immediately - this is not so, the second console.log may also be hit - along with any other code you may have. This is not guaranteed, however, so execution may abruptly stop at some point.
Example producing correct results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
else {
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
}
})
In this version, you will see exactly 1 line of console.logs - as I was originally intending.

Retry Google Cloud function on arbitrary interval

I've got a Google Cloud app with several cloud functions that call an API, then save the response in Firebase. I have this scheduled function set up to retry the API on error and it works great. But I want to retry the call if the data I need isn't there yet. Calling again right away could work if I throw an error, but it's highly unlikely that the missing data will be available seconds later, so I'd rather check once an hour after that until I have valid data.
Below is a shortened version of my function. I can imagine adding a setTimeout and having the function call itself again, but I'm not sure I would do that or if it's a good idea since it would keep the function alive for a long time. Is there a way to automatically retry this scheduled function on an arbitrary time interval?
exports.fetchData= functions.pubsub
.schedule('every Tuesday 6:00')
.timeZone('America/New_York')
.onRun(async context => {
const response = fetch(...)
.then(res => {
if (res.status < 400) {
return res;
} else {
throw new Error(`Network response was not ok. ${res}`);
}
})
.then(res => res.json());
const resObj = await response;
resObj.map(x => {
// check response for valid data
})
if (// data is valid) {
// save to Firebase
} else {
// retry in 1 hour
}
});
});
Scheduled functions only run on the schedule you specify. There is no "arbitrary" scheduling. If you think that the function might frequently fail, consider just increasing the frequency of the schedule, and bail out of function invocations that don't need to run because of recent success.
If you enable retries, and the function generates an error by throwing an exception, returning a rejected promise, or timing out, then Cloud Functions will automatically retry the function on a schedule that you can't control.
setTimeout is not a feasible option to keep a function alive for longer than its configured timeout. Cloud Functions will terminate the function and all of its ongoing work after the timeout expires (and you would be paying for the time the function sits idle, which is kind of a waste).

Continue execution after sending response (Cloud Functions for Firebase)

I'm using Firebase Functions with https triggers, and I was wondering how long after sending the response to the client, the functions keeps executing. I want to send a response to the client and then perform another operation (send a mail).
Currently I'm doing this as following:
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
doSomeAsyncJob()
.then(() => {
res.send("Ok");
})
.then(() => {
emailSender.sendEmail();
})
.catch(...);
});
The above code is working for me, but I'm suspecting that the code only works because sending the mail has finished before the res.send has completed, so I was wondering how exactly the termination process is working to make sure the code will not break.
You should expect that the HTTP function terminates the moment after you send the response. Any other behavior is some combination of luck or a race condition. Don't write code that depends on luck.
If you need to send a response to the client before the work is fully complete, you will need to kick off a second function to continue where the HTTP function left off. It's common to use a pub/sub function to do with. Have the HTTP function send a pub/sub message to another function, then terminate the HTTP function (by sending a response) only after the message is sent.
If the expected response is not pegged to the outcome of the execution, then you can use
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
res.write('SUCCESS')
return doSomeAsyncJob()
.then(() => {
emailSender.sendEmail();
})
.then(() => {
res.end();
})
.catch(...);
});
This sends back a response a soon as a request is received, but keeps the function running until res.end() is called
Your client can end the connection as soon as a response is received back, but the cloud function will keep running in the background
Not sure if this helps, but it might be a workaround where the client needs a response within a very limited time, considering that executing pub/sub requires some extra processing on its own and takes time to execute
TL;DR
While https functions will terminate shortly after res.send(), it is not guaranteed that 0 lines of code after res.send() will be executed.
I think a fuller answer has 2 components:
as Doug pointed out, do not put any additional code you expect to be executed after res.send()
cloud functions will terminate shortly after res.send(), but don't expect that exactly 0 lines of code will be executed
I ran into a situation where for a db maintenance script, if no records met my criteria, I said goodbye with res.send() and had additional logic after it. I was expecting that piece not to be run, since I've already terminated the request.
Example producing unexpected results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
})
In the above example, I was expecting res.send() to terminate the function immediately - this is not so, the second console.log may also be hit - along with any other code you may have. This is not guaranteed, however, so execution may abruptly stop at some point.
Example producing correct results:
exports.someFunction = functions.https.onRequest((req, res) => {
if (exitCriteria === true) {
// we can exit the function, nothing to do
console.log('Exit criteria met')
res.status(200).send()
}
else {
// code to handle if someCriteria was falsy
console.log('Exit criteria not met, continue executing code')
}
})
In this version, you will see exactly 1 line of console.logs - as I was originally intending.

Resources