The Firebase function for updating the counter is experiencing delays

My real-time database has a counter variable that increments with each function call. However, if multiple users trigger the function simultaneously when the counter is at 1, they both get the same value for the counter (1) instead of it incrementing to 3 as expected.

datasnapshot.child("counter").val() = 1 

and both of them will set

promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1))) = 2

while it should be 3

I am looking for a solution to make the updates faster or ensure that one function call waits for the previous one to finish before running. I am relatively new to Firebase and TypeScript, so any help would be appreciated.

This is my current code:

exports.functionTST = functions.https.onCall(async (data, context) => {
  const promises = [];
  JSON.parse(JSON.stringify(data), (key, value) => {
     //parse values
  });

  //set values
  promises.push(admin.database().ref('game/' + matchkey + '/'...).update({
      val1: val2,
    }))

    //counter
    admin.database().ref('game/' + matchkey).once('value')
      .then(datasnapshot => {
        let counter = datasnapshot.child("counter").val()
        if (counter === null) {
          promises.push(admin.database().ref('game/' + matchkey + '/counter').set(1))
        } else if (counter < 3) {
          promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1)))
        } else if (counter == 3) {
          promises.push(admin.database().ref('game/' + matchkey + '/counter').remove())
          //do other stuff
         }
     });

    Promise.all(promises);
    return null
  }
});

Any assistance on resolving this issue would be greatly appreciated! Thank you!

Answer №1

Let's begin by correcting the indentation in your code.

exports.functionTST = functions.https.onCall(async (data, context) => {
  const promises = [];
  JSON.parse(JSON.stringify(data), (key, value) => {
     // parse values 
  });

  // set values
  promises.push(
    admin.database()
      .ref('game/' + matchkey + '/'...)
      .update({
        val1: val2,
      })
  );

  // counter
  admin.database()
    .ref('game/' + matchkey)
    .once('value')
    .then(datasnapshot => {
      let counter = datasnapshot.child("counter").val();
      if (counter === null) {
        promises.push(admin.database().ref('game/' + matchkey + '/counter').set(1));
      } else if (counter < 3) {
        promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1)));
      } else if (counter == 3) {
        promises.push(admin.database().ref('game/' + matchkey + '/counter').remove());
        // do other stuff
      }
    });

  Promise.all(promises);
  return null;
});

Your code has a few issues that need to be addressed.

The main problem is that you are not properly handling the asynchronous nature of your Promises. Failing to await or return the Promise chain correctly can lead to throttling and potential skipping of network requests like database updates.

await Promises.all(promises); // <- added await here
return null; // indicating no data to return

You should modify this section to either:

await Promises.all(promises); 
return null; // indicating no data to return

or

return Promises.all(promises)
  .then(() => null); // indicating no data to return

The following block of code seems redundant as it doesn't store the result of JSON.parse. Consider using Object.entries(data) based on your requirement.

JSON.parse(JSON.stringify(data), (key, value) => {
  // parse values
});

This part creates a stray Promise without proper handling, leading to inconsistent behavior. It needs to be stored in the promises array for consistency.

admin.database()
  .ref('game/' + matchkey)
  .once('value')
  .then(/* ... */);

You should replace the above block with a transaction operation to streamline database updates and ensure accuracy.

// set values
promises.push(
  admin.database()
    .ref('game/' + matchkey + '/'...)
    .update({
      val1: val2,
    })
);

// counter
admin.database()
  .ref('game/' + matchkey)
  .once('value')
  .then(datasnapshot => {
    let counter = datasnapshot.child("counter").val();
    if (counter === null) {
      promises.push(admin.database().ref('game/' + matchkey + '/counter').set(1));
    } else if (counter < 3) {
      promises.push(admin.database().ref('game/' + matchkey + '/counter').set((counter + 1));
    } else if (counter == 3) {
      promises.push(admin.database().ref('game/' + matchkey + '/counter').remove());
      //do other stuff
    }
  });

Promises.all(promises);
return null;

By implementing the above changes and merging your logic, the function can be simplified like so:

//counter
await admin.database() 
  .ref('game/' + matchkey)
  .transaction((matchInfo) => {
    if (matchInfo) {
      matchInfo[/* The '...' from .ref('game/' + matchkey + '/'...) */].val1 = val2;
      
      const counter = matchInfo.counter || 0;
      if (counter < 3) {
        matchInfo.counter = counter + 1;
      } else {
        delete matchInfo.counter;
        // handle additional tasks like declaring a winner
      }
    }
    return matchInfo;
 });

return null;

The final version of your function could resemble the following structure:

exports.functionTST = functions.https.onCall(async (data, context) => {
  
  try {
    const matchkey = data.matchkey;
    /* ... */
  } catch (err) {
    console.log('Unexpected error while handling request data: ', err);
    throw new functions.https.HttpsError('invalid-argument', 'Could not parse request data');
  }

  try {
    await admin.database() 
      .ref('game/' + matchkey)
      .transaction((matchInfo) => {
        /* ... */
      });
  } catch (err) {
    console.log('Unexpected error while updating match information: ', err);
    throw new functions.https.HttpsError('internal', 'Could not update match information');
  }
  
  return null;
}

Answer №2

Following a deployment, the initial run may experience delays due to the occurrence of a "cold start." It is advisable to implement transactions in order to mitigate any potential race conditions that may arise.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Navigating the itemlist HTML to extract and manipulate data in a Flask application

In my current project, I am attempting to retrieve a value from an array in Flask based on the user's selection. Additionally, I need to perform calculations on this value within Flask as well. Below is the code snippet: HTML Code: <div class="f ...

Is it necessary for me to authenticate jwt tokens?

Let me explain my situation. I have generated a JWT token and stored it in Redis with a TTL of 1 hour. Now, most tutorials suggest using jwt.verify to authenticate the token. I understand that jwt.verify is used to verify whether the token is authentic or ...

How can I ensure that Chakra UI MenuList items are always visible on the screen?

Currently, I am utilizing Chakra UI to design a menu and here is what I have so far: <Menu> <MenuButton>hover over this</MenuButton> <MenuList> <Flex>To show/hide this</Flex> </MenuList> </ ...

Ways to assign a CSS class specifically for images

.calendarList { background-image: url('/resource3/hpsc/common/images/calendar.png'); background-position: 135px 50%; background-repeat: no-repeat; cursor:pointer; } <input type="text" id="toDatepicker" class="cal ...

Encountering a TypeScript error within the queryFn while implementing Supabase authentication alongside React Toolkit Query

I've been attempting to integrate Supabase authentication with React Toolkit Query but encountering an issue with the utilization of the queryFn. Here is the code snippet that employs supabase.auth.signUp to register a user using email/password. You ...

Using Vue.js with Vue Router to handle login functionality and automatically refreshing data

I am currently working on a Vue.js application and have successfully implemented authentication functionality, including checking if the user is logged in. After logging in, I want to fetch some data and store it in localStorage. However, I have encounter ...

Having trouble with moving the svg:svg element?

I'm struggling to move an svg element that is nested within another svg. I am trying to directly manipulate the x and y values, but I keep encountering a "read-only" error. I attempted to use transform instead, but it doesn't seem to have any eff ...

How to allow users to input strings on web pages with JavaScript

I created a Language Translator using java script that currently translates hardcoded strings in an HTML page. I want to enhance its flexibility by allowing users to input a string into a textbox/textarea for translation. Any assistance would be greatly a ...

Is there a way to verify duplicate email addresses without the need to click any button?

As I work on developing a web application, I am faced with the challenge of checking for duplicate email addresses in real-time without the need to press a button. This check must be done by comparing the data with information stored in the database. Since ...

Refreshing a Next.js page results in a 404 error

I've set up a Next.js page called page.js that can be accessed through the URL http://localhost:3000/page. However, I have a need to access this page through a different URL, namely http://localhost:3000/my-page. To achieve this, I decided to utilize ...

Any ideas on how I can use PHP or JavaScript to repeatedly execute a segment of HTML code?

I recently tried using a for loop and heredoc in PHP with code that looks something like this: $options = ''; for($Year = date("Y"); $Year <= date("Y") + 5; $Year++) { $options .= "<option>$Year</option>\n"; } $Select = ...

What causes jQuery to output individual characters instead of the entire content of the span element when looping through it?

I am facing an issue with a group of span elements that have the "endtime" class. My goal is to retrieve each of their contents separately from one another. <span class='endtime'> 2011-03-29 00:01:03 </span> <span class='e ...

The functionality of angular-ui's ui-utils and ui-scroll module is currently nonfunctional in version 0.1.0

I have been trying to implement the features from this Angular UI library: http://angular-ui.github.io/ui-utils/, particularly focusing on this aspect: https://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md Unfortunately, despite my e ...

Guide to using a TypeScript interface in a JSON file with Visual Studio Code

Imagine having a TypeScript interface as follows: interface Settings { id?: number tag: string } Is there a way to ensure that all .json files within a specific directory adhere to these requirements? If VS Code does not offer this functionality, ...

In what situations can the comma operator be beneficial?

After reading a question about the "comma operator" in expressions and exploring the MDN documentation on it, I am struggling to identify a practical scenario where it would be beneficial. Therefore, in what situations does the comma operator prove useful ...

Is it possible to determine if a selected date falls within the current week using JavaScript?

Currently facing an issue with JavaScript. I have multiple dates retrieved from a database, and I need to extract the date that falls within the current week. ...

navigating to the start of a hyperlink

I'm having issues with scrolling to anchors and encountering 3 specific problems: If I hover over two panels and click a link to one of them, nothing happens. When I'm on section D and click on section C, it scrolls to the end of section C. ...

Implement a class attribute to the parent <div> element using React and TypeScript

I'm trying to figure out how to achieve this task. I need to assign a class upon clicking on an element that is not directly in my code, but rather in one of its parent elements. My initial thought was to accomplish this with jQuery using the followi ...

Issue with Bootstrap Table Style When Using window.print();

The color of my Bootstrap table style is not displaying correctly in the print preview using window.print(). Here is a screenshot showing that the table style is not working properly: https://i.stack.imgur.com/eyxjl.jpg Below is the code I am using: < ...

VueJS: components unable to access global variables

I am currently in the process of transitioning my front-end to a gulp-based application. I seem to be encountering some issues with Vue.js as I am facing two errors: [Vue warn]: Property or method "params" is not defined on the instance but referenced dur ...