I've recently begun working with Firebase Cloud Functions, and I'm slightly confused as to how to appropriately terminate this HTTP function:
exports.UpdateUserInfo= functions.https.onRequest( async (request, response) => {
try{
//Make a read call to Realtime DB
const snapshot = await admin.database().ref('/UserData').get()
if (snapshot.exists() == false) {
response.send("No users found")
return null
}
//All of the remaining code within the scope of the try block executes
functions.logger.log("Wait, this function should have ended")
let updateUserInfo = await userInfoUpdater(response)
}
catch{
functions.logger.info(error);
response.status(500).send("ERROR")
}
})
From what I've read, the correct way to terminate an HTTP function is to send a response via the response object. However, it appears that unless I include a final call to return null, the function continues to execute beyond its intended lifespan. Even worse, the function terminates and still allows the execution of additional network calls making things quite unpredictable and unorganized (see logs). I'd like to prevent the function from continuing once the conditional is met. Is returning null the best way to ensure proper termination, or am I missing something?
Calling response.send() does not terminate the function immediately. It merely signals to Cloud Functions that the function should shut down after the current block of code returns, either by return statement or falling off the end of the function block. If the function doesn't return in one of these two ways, then Cloud Functions will either time out or have other problems as the CPU is clamped down soon after the response is sent.
Essentially, sending the response should be the very last thing the function does before it returns. Anything else is prone to error.
Related
I was wondering if there is any to cancel / stop execution of a javascript function that contains multiple await functions. Due to the nature of promises and their lack of proper cancellations, is there any other implementation or library to help me achieve something like this?
async function run(x,y,z) {
return new Promise(async(resolve,reject) => {
await doSomething(x)
await doSomething(y)
//cancel could be happen around here and stop the last "doSomething"
await doSomething(z)
})
}
setTimeout(() => {
run.cancel()
},500) //cancel function after 500ms
To just stop the advancement from one function call to the next, you can do something like this:
function run(x, y, z) {
let stop = false;
async function run_internal() {
await doSomething(x)
if (stop) throw new Error("cancelled");
await doSomething(y)
if (stop) throw new Error("cancelled");
await doSomething(z)
}
return {
cancel: () => {
stop = true;
},
promise: run_internal();
};
}
const retVal = run(a, b, c);
retVal.promise.then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
setTimeout(() => {
retVal.cancel()
}, 500); //cancel function after 500ms
Javascript does not have a generic way to "abort" any further execution of a function. You can set a flag via an external function and then check that flag in various points of your function and adjust what you execute based on that flag.
Keep in mind that (except when using workerThreads or webWorkers), Javascript runs your code in a single thread so when it's running, it's running and none of your other code is running. Only when it returns control back to the event loop (either by returning or by hitting an await) does any of your other code get a chance to run and do anything. So, "when it's actually running", your other code won't be running. When it's sitting at an await, your other code can run and can set a flag that can be checked later (as my example above shows).
fetch() in a browser has some experimental support for the AbortController interface. But, please understand that once the request has been sent, it's been sent and the server will receive it. You likely won't be aborting anything the server is doing. If the response still hasn't come back yet or is in the process of coming back, your abort may be able to interrupt that. Since you can't really know what is getting aborted, I figure it's better to just put a check in your own code so that you won't process the response or advance to further processing based on a flag you set.
You could wrap this flag checking into an AbortController interface if you want, but it doesn't change the fundamental problem in any way - it just affects the API you expose for calling an abort.
The way to actually use cancellation is through the AbortController which is available in the browser and on Node 15+
Node reference: https://nodejs.org/api/globals.html#class-abortcontroller
MDN reference: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
Some APIs are currently using out of the box the abort signal like fetch in the browser or setTimeout timers API in Node (https://nodejs.org/api/timers.html#timerspromisessettimeoutdelay-value-options).
For custom functions/APIs you need to implement it by yourself but it's highly encouraged to follow the Abort signal methodology so you can chain both custom and oob functions and make use of a single signal that does not need translation
I'm having trouble understanding control flow with asynchronous programming in JS. I come from classic OOP background. eg. C++. Your program starts in the "main" -- top level -- function and it calls other functions to do stuff, but everything always comes back to that main function and it retains overall control. And each sub-function retains control of what they're doing even when they call sub functions. Ultimately the program ends when that main function ends. (That said, that's about as much as I remember of my C++ days so answers with C++ analogies might not be helpful lol).
This makes control flow relatively easy. But I get how that's not designed to handle event driven programming as needed on something like a web server. While Javascript (let's talk node for now, not browser) handles event-driven web servers with callbacks and promises, with relative ease... apparently.
I think I've finally got my head around the idea that with event-driven programming the entry point of the app might do little more than set up a bunch of listeners and then get out of the way (effectively end itself). The listeners pick up all the action and respond.
But sometimes stuff still has to be synchronous, and this is where I keep getting unstuck.
With callbacks, promises, or async/await, we can effectively build synchronous chains of events. eg with Promises:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
});
Great. I've got a series of tasks I can do in order -- kinda like more traditional synchronous programming.
My question is: sometimes you need to deviate from the chain. Ask some questions and act differently depending on the answers. Perhaps conditionally there's some other function you need to call to get something else you need along the way. You can't continue without it. But what if it's an async function and all it's going to give me back is a promise? How do I get the actual result without the control flow running off and eloping with that function and never coming back?
Example:
I want to call an API in a database, get a record, do something with the data in that record, then write something back to the database. I can't do any of those steps without completing the previous step first. Let's assume there aren't any sync functions that can handle this API. No problem. A Promise chain (like the above) seems like a good solution.
But... Let's say when I call the database the first time, the authorization token I picked up earlier for it has expired and I have to get a new one. I don't know that until I make that first call. I don't want to get (or even test for the need for) a new auth token every time. I just want to be able to respond when a call fails because I need one.
Ok... In synchronous pseudo-code that might look something like this:
let Token = X
Step 1: Call the database(Token). Wait for the response.
Step 2: If response says need new token, then:
Token = syncFunctionThatGetsAndReturnsNewToken().
// here the program waits till that function is done and I've got my token.
Repeat Step 1
End if
Step 3: Do the rest of what I need to do.
But now we need to do it in Javascript/node with only async functions, so we can use a promise (or callback) chain?
let Token = X
CallDatabase(Token)
.then(check if response says we need new token, and if so, get one)
.then(...
Wait a sec. That "if so, get one" is the part that's screwing me. All this asynchronicity in JS/node isn't going to wait around for that. That function is just going to "promise" me a new token sometime in the future. It's an IOU. Great. Can't call the database with an IOU. Well ok, I'd be happy to wait, but node and JS won't let me, because that's blocking.
That's it in a (well, ok, rather large) nutshell. What am I missing? How do I do something like the above with callbacks or Promises?
I'm sure there's a stupid "duh" moment in my near future here, thanks to one or more of you wonderful people. I look forward to it. 😉 Thanks in advance!
What you do with the .then call is to attach a function which will run when the Promise resolves in a future task. The processing of that function is itself synchronous, and can use all the control flows you'd want:
getResponse()
.then(response => {
if(response.needsToken)
return getNewToken().then(getResponse);
})
.then(() => /* either runs if token is not expired or token was renewed */)
If the token is expired, instead of directly scheduling the Promise returned by .then, a new asynchronous action gets started to retrieve a new token. If that asynchronous action is done, in a new task it'll resolve the Promise it returns, and as that Promise was returned from the .then callback, this will also then resolve the outer Promise and the Promise chain continues.
Note that these Promise chains can get complicated very quick, and with async functions this can be written more elegantly (though under the hood it is about the same):
do {
response = await getResponse();
if(response.needsToken)
await renewToken();
} while(response.needsToken)
Fist of all, I would recommend against using then and catch method to listen to Promise result. They tend to create a too nested code which is hard to read and maintain.
I worked a prototype for your case which makes use of async/await. It also features a mechanism to keep track of attempts we are making to authenticate to database. If we reach max attempts, it would be viable to send an emergency alert to administrator etc for notification purposes. This avoid the endless loop of trying to authenticate and instead helps you to take proper actions.
'use strict'
var token;
async function getBooks() {
// In case you are not using an ORM(Sequelize, TypeORM), I would suggest to use
// at least a query builder like Knex
const query = generateQuery(options);
const books = executeQuery(query)
}
async function executeQuery(query) {
let attempts = 0;
let authError = true;
if (!token) {
await getDbAuthToken();
}
while (attemps < maxAttemps) {
try {
attempts++;
// call database
// return result
}
catch(err) {
// token expired
if (err.code == 401) {
await getDbAuthToken();
}
else {
authError = false;
}
}
}
throw new Error('Crital error! After several attempts, authentication to db failed. Take immediate steps to fix this')
}
// This can be sync or async depending on the flow
// how the auth token is retrieved
async function getDbAuthToken() {
}
In Tips & Tricks the docs say
Note: If a Node.js background function returns a Promise, Cloud Functions ensures that the Promise is settled before terminating.
My question is, does this ensurance extend to anonymous callbacks that are returned from a non-promised method?
export const mutateData = functions.https.onCall(async (data, context)=>{
const data = await getAndMutateData();
await Promise.all([
furtherMutate(data.data1),
furtherMutate(data.data2),
furtherMutate(data.data3)
]);
const addendum = {
addedData1: foo,
addedData2: bar,
addedData3: foobar
};
// Not awaited, we want to do this in the background while the client gets the status back
data.writeCallback(addendum);
return "success";
}
async function getAndMutateData(){
let [data1, data2, data3] = await Promise.all([firestoreGetter1(), firestoreGetter2(), firestoreGetter3()]);
mutate(data1);
mutate(data2);
mutate(data3);
return {
data1: data1,
data2: data2,
data3: data3,
writeCallback: async function(addendum): Promise<any>{
return Promise.all([
firestoreSetter1(data1, addendum.addedData1), // Merges data streams
firestoreSetter2(data2, addendum.addedData2),
firestoreSetter3(data3, addendum.addedData3)
]);
}
}
}
Will the cloud instance close before the writeCallback has had a chance to finish? I want to give the client the success/fail status without waiting for the firestore writes to complete. There are other failsafes in place for that case.
Firstly, the tip you cite applies only to background functions. The callable function you show here is not a background function. HTTP and callables are synchronous with the client that invokes them.
Will the cloud instance close before the writeCallback has had a chance to finish?
Yes, it will. You will need to await it if you want the function to complete before the function shuts down forcibly. You can't leave async work "dangling" when the function terminates, otherwise it might have strange behavior, or simply not work at all.
For callable functions, you are obliged to return a promise that becomes fulfilled with the data to send to the client, only after all the async work is complete. If you are trying to allow some work to continue after the client receives its response, you should refer to these:
Continue execution after sending response (Cloud Functions for Firebase)
Call cloud functions without waiting for response
The most common way of continue work after sending a response is to write a pubsub function to perform the extra work, then send a message to it from the main function.
I have a cloud function in Firebase that, among a chain of promise invocations, ends with a call to this function:
function sendEmail() {
return new Promise((accept) => {
const Email = require('email-templates');
const email = new Email({...});
email.send({...}).then(() => {
console.log('Email sent');
}).catch((e) => {
console.error(e);
});
accept();
});
}
I am well aware of the fact that email.send() returns a promise. There's a problem however with this approach, that is, if I were to change the function to be:
function sendEmail() {
const Email = require('email-templates');
const email = new Email({...});
return email.send({...});
}
It usually results in the UI hanging for a significant amount of time (10+ seconds) because the time it takes from the promise to resolve equals the amount of time it takes for the email to send.
That's why I figured the first approach would be better. Just call email.send() asynchronously, it'll send the email eventually, and return a response to the client whether the email has finished its round trip or not.
The first approach is giving me problems. The cloud function finishes execution must faster, and thus ends up being a better experience for the user, however, the email doesn't send for another 15+ minutes.
I am considering another approach where we have a separate cloud function hook that handles the email sending, but I wanted to ask StackOverflow first.
I think there are two aspects being mixed here.
One side of the question deals with promises in the context of Cloud Functions. Promises in Cloud Functions need to be resolved before you call res.send() because right after this call the function will be shutdown and there's no guarantee that unresolved promises will complete before the function instance is terminated, see this question. You might as well never call res.send() and instead return the result of a promise as shown in the Firebase documentation, the key here would be to ensure the promise is resolved properly for example using an idiom like return myPromise().then(console.log); which will force the promise resolution.
Separately, as Bergi pointed out in the comments the first snippet uses an anti-pattern with promises and the second one is way more concise and clear. If you're experiencing a delay in the UI it's likely that the execution gets freezed waiting for the Function response and you might consider whether this could be avoided in your particular use case.
All that said, your last idea of creating a separate function to deal with the email send process would also likely reduce the response time and could even make more sense from a separation of concerns point of view. To go this route I would suggest to send a PubSub message from the main function so that a second one sends the email. Moreover, PubSub triggered function allows to configure retry policies which may be useful to ensure the mail will be sent in the context of eventual errors. This approach is also suggested in the question linked above.
I need to get some data from a grpc response and return that data. How can I wait for the data to be ready in order to run the rest of my code and return the data?
I tried placing a while loop to wait for the response of the function but it just gets stuck there forever. I need to either update some global variables or try to capture the response from the requestQueryTreeItemCommand function.
I know I probably must use some kind of callback or Promise, but I just don't know how to hook it up to this code.
function queryTreeItem(elem) {
var requestQueryTreeItemCommand = new messages.QueryTreeItemRequest();
requestQueryTreeItemCommand.setItem(elem.textContent);
function queriedItemCallbackFunc(err, response) {
if (err) {
console.log(err);
responded = true;
response_str = "";
return response_str;
} else {
responded = true;
response_str = response.getMessage();
return response_str;
}
}
client.queryTreeItem(requestQueryTreeItemCommand, queriedItemCallbackFunc); // Requests stuff and calls queriedItemCallbackFunc when the other side responds back.
// while (!responded) {
// console.log("JS-DEBUG");
// console.log(responded);
// console.log(response_str);
// }
// While loop doesn't work, keeps looping forever.
// Somehow wait for queriedItemCallbackFunc to update the global variable 'response_str',
// or somehow capture the return of queriedItemCallbackFunc and don't use global variables.
responded = false;
return response_str;
}
Fundamentally, it is impossible to do exactly what you are asking for here. A gRPC request is an asynchronous operation, and returning from a function is a synchronous operation. You cannot wait for an asynchronous operation to complete before returning from a function.
You can approximate the code appearance of synchronous code using async/await, but that would just push the problem one layer up. Doing that would make queryTreeItem asynchronous, and then whatever function calls it would have the same problem you have now.
The only real solution here is to code more broadly in an asynchronous style, using callbacks and/or promises and/or async/await instead of return values to handle the results of asynchronous actions.