policenauts
policenauts

Reputation: 542

Proper way to await multiple async functions with Google Cloud Functions Node.JS

I am relatively new to the async world of Javascript. My function makes some calls to the Firebase Admin SDK and also some fetch requests to a third party API. The function works, but every once in a while I get a socket hang up error. I essentially want to wait until all the await functions complete before sending res.end(), but it looks like I am doing something wrong in terms of my use of async/await.

My function finishes (looks like it hits res.end()) but it keeps continuing on:

enter image description here

And then strangely enough, an error from the same execution ID shows up in the following execution ID:

enter image description here

Here is how I am structuring my code:

exports.myFunction = async (req, res) => {

    // parse the body...

    // if it's a closed order 
    if (json.hasOwnProperty('closed_at')) {

        // get order object from orders directory
        await ordersRef.once("value", async function(snapshot) {
            var orderResponseObj = snapshot.val();

            // do some stuff with orderResponseObj

            // ** res.end() CALLED BEFORE THIS HAPPENS **
            await deleteRef.once("value", async function(snapshot) {
                var deleteResponseObj = snapshot.val();
                // do some stuff with the delete object
            });

            // get object from shop directory
            await shopInfoRef.once("value", async function(snapshot) {
                var firebaseResponseObj = snapshot.val();

                await updateInventory.updateInventory(); // do some fetch request to 3rd party API

                await deleteProduct.deleteProduct(); // do another fetch call to 3rd party API
            });
        });
    }
    // terminate the cloud function
    res.end();
}

Upvotes: 3

Views: 2238

Answers (1)

DDomen
DDomen

Reputation: 1878

So you have some nested promises that will not actually wait all of them to be completed, your structure is like this:

|   1 exports.myFunction
|   |   1 ordersRef.once
|   |   |   1 deleteRef.once
|   |   |   2 shopInfoRef.once
|   |   |   |   1 updateInventory.updateInventory
|   |   |   |   2 deleteProduct.deleteProduct

The fact is that your async functions will only await for the first lower level promises, so for example deleteRef.once will just await for shopInfoRef.once, but not for updateInventory.updateInventory. This means that your top level exports.myFunction will wait only that ordersRef.once resolves, ignoring the rest. The numbers indicate the order of the execution of the promises, since you are using await for all of them, we have no promise on the same level firing togheter (no duplicated numbers).

Now in order to wait for the end of all the function cycles you can implement your own chain of promises. Looking at your code, the very last promise to await is the deleteProduct.deleteProduct, because when you reach that point, any other promise is resolved, because of all the await keywords.

exports.myFunction = async (req, res) => {

    // parse the body...

    // if it's a closed order 
    if (json.hasOwnProperty('closed_at')) {
     let awaiter = new Promise((resolve) => {
        // get order object from orders directory
        await ordersRef.once("value", async function(snapshot) {
            var orderResponseObj = snapshot.val();

            // do some stuff with orderResponseObj

            // ** res.end() CALLED BEFORE THIS HAPPENS **
            await deleteRef.once("value", async function(snapshot) {
                var deleteResponseObj = snapshot.val();
                // do some stuff with the delete object
            });

            // get object from shop directory
            await shopInfoRef.once("value", async function(snapshot) {
                var firebaseResponseObj = snapshot.val();

                await updateInventory.updateInventory(); // do some fetch request to 3rd party API

                await deleteProduct.deleteProduct(); // do another fetch call to 3rd party API

                resolve(); // we put resolve in the very last point of the chain
            });
        });
      });

      // await make the execution of your async function
      // wait until the "resolve" function of our "awaiter"
      // promise is called, so at the very last of the chain
      await awaiter;
    }
    // terminate the cloud function
    res.end();
}

Obviously you can contract the code in a form like await new Promise(resolve => { ... }), but I divided the two sentences just for sake of clarity.

The new structure results like this:

|   1 exports.myFunction
|   |   1 awaiter
|   |   -   1 ordersRef.once
|   |   -   |   1 deleteRef.once
|   |   -   |   2 shopInfoRef.once
|   |   -   |   |   1 updateInventory.updateInventory
|   |   -   |   |   2 deleteProduct.deleteProduct
|   |   ----------> 3 awaiter.resolve

Upvotes: 4

Related Questions