1

If I choose not to wait for an async call to complete inside a firebase function, what happens to the async call when the function completes? On what thread does it continue, does it still run within the function's runtimeOpts and how does this affect usage?

import * as firebase from 'firebase';
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

exports.action = functions.https.onRequest((req, res) => {
    admin.database().ref('action').add({ 1: 2 });
    res.end(); // or just `return;` for other functions
});

Or maybe even

exports.action2 = functions.https.onRequest((req, res) => {
    new Promise(resolve => setTimeout(() => admin.database().ref('action').add({ 1: 2 })}, 10_000));
    res.end();
});
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
Raven
  • 1,453
  • 3
  • 18
  • 29

1 Answers1

3

Have a look at this section of the documentation on Cloud Functions, which explains why it is "important to manage the lifecycle of a function to ensure that it resolves properly".

If you want to be sure that "the Cloud Functions instance running your Cloud Function (CF) does not shut down before your CF successfully reaches its terminating condition or state" (in your case, when the asynchronous work is done), you need to "resolve your CF by returning a JavaScript promise".

So, if you don't do that, there is the risk that the Cloud Functions instance running your CF shuts down before your calls to one or more async functions complete.

However, it does NOT mean that this will always happen. It may be very well possible that the Cloud Functions instance running your CF continues to run after you sent back the response to the client (with res.end();) resulting in the async work being completed.

The problem is that this is totally out of your control (it depends on how the Cloud Functions platform manages you Cloud Function instances): sometimes it may continue running (completing the async work), sometimes it may not.

You will find a lot of questions on Cloud Functions on Stack Overflow which explain that their problem is that the Cloud Function sometimes executes the async work correctly and sometimes not: the reason is simply because they don't return the Promises returned by the async calls.

Does this affect usage?

Yes, as clearly said in the doc: "By terminating functions correctly, you can avoid excessive charges from functions that run for too long or loop infinitely."


So, concretely, in your case you should do something like:

exports.action = functions.https.onRequest(async (req, res) => {  //  <-- Note the async keyword here
    await admin.database().ref('action').add({ 1: 2 });
    res.end(); // or just `return;` for other functions
});

Note that if your Cloud Function is synchronous (it does not call any asynchronous method) you have to terminate it with a simple return; statement (or by calling send(), redirect(), or end() for an HTTPS Cloud Function). For example:

exports.action = functions.https.onRequest((req, res) => {
    const result = 1 + 3;
    res.status(200).send(result);
});

or

exports.unusefulCloudFunction = functions.firestore
  .document('users/{userId}').onWrite((change, context) => {
    const result = 1 + 3;
    console.log(result);
    return;
  });
Raven
  • 1,453
  • 3
  • 18
  • 29
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • 1
    So if a function returns with a Promise the function runner will make sure to continue until the promise is resolved? Your answer is insightful. I guess that would mean that if you're running multiple async calls in parallel you need to have the return value wait for or return `Promise.all` so that nothing is running when you complete the call. It also means I can't really return a value to the user fast even if they're not dependent on the result of a slow async call. – Raven Jan 25 '20 at 15:51
  • Yes you are right: if you correctly manage to return the different Promises returned by the asynchronous operations, you can be sure that the Cloud Functions instance running your function will **not** shut down before your function successfully reaches its terminating condition or state. In case "you're running multiple async calls in parallel" you indeed have to use `Promise.all`. In case the asynchronous operations are sequential (e.g.: I read the DB and use the returned value to read another document) you need to chain the Promises with `then()`. – Renaud Tarnec Jan 25 '20 at 15:58
  • "It also means I can't really return a value to the user fast even if they're not dependent on the result of a slow async call" -> If there is not async work to be executed in your function, you can immediately return after the CF has done the synchronous work. The doc says "Terminate a **synchronous** function with a `return;` statement." – Renaud Tarnec Jan 25 '20 at 15:59
  • @RenaudTarnec Be very careful about `functions.https.onRequest` as it's the only Cloud Function handler that returns `void`, which means that it will not wait for Promises - even those using async/await syntax. [source](https://github.com/firebase/firebase-functions/blob/master/src/providers/https.ts#L35-L54) – samthecodingman Jan 25 '20 at 17:43
  • @samthecodingman Thanks for your comment. Yes, I know that for HTTPS CF you don't need to return the promise (or promises chain). It is explained in the doc ("Terminate HTTP functions with `res.redirect()`, `res.send()`, or `res.end()`") and it is also clear in the [official video](https://youtu.be/7IkUgCLr5oA?t=507) as well as in some of the [official samples](https://github.com/firebase/functions-samples/blob/62dfb26c06b067a84a1a428e9b967d51d0149a55/quickstarts/uppercase-firestore/functions/index.js#L32-L42) (to be continued) – Renaud Tarnec Jan 26 '20 at 10:24
  • (continued) IMHO I don't think it makes any difference with regards to the fact that you need to wait for the async work to finish before "sending the signal" the CF is completed (calling `res.send()` **OR** returning a Promise). I do agree, on the other hand, that my answer could be more clear on this point, clearly separating those two different ways of sending the signal the CF is completed. I'll adapt it to better reflect this two ways :-) – Renaud Tarnec Jan 26 '20 at 10:25
  • 1
    @RenaudTarnec It's mainly a suggested improvement because your answer heavily discusses Promises (fantastically) however, the second paragraph is misleading due to missing this important caveat in the body of your answer regarding `onRequest` , the specific example in the question. The written documentation is light on the fact that returning a Promise for an `onRequest` will not keep the function alive and I chimed in for future readers who might get here from Google. – samthecodingman Jan 27 '20 at 16:23