What strategies can I use to prevent making multiple API calls inside setInterval when initializing a new connection in a socket?

I am currently working on implementing a socket system where users can request a function with an ID, and after receiving the ID, I send requests to an API every second and emit the response back to the user.

Issue: Every time a new user requests the same ID, it triggers the API call again with the same ID.

Goal: My objective is to ensure that if setInterval is already running with the same ID, it should not trigger another API call but simply start emitting the response.

This is my current code:

socket.on('fancy' , async (data) => {
    checkInterval = setInterval(async _ => {
      callapi(data.match_id).then( fancy => {
        socket.emit(`fancy_${data.match_id}`, fancy)
      })
    }, 1000)
  })

callapi = (id) => {
  var options = {
    'method': 'POST',
    'url': 'url',
    formData: {
      'id': id
    }
  };
  return new Promise((resolve, reject) => {
    request(options, function (error, response) {
      let resp = {}
      if (error) reject(error)
      resolve({status: 'success', data: JSON.parse(response.body).data})
    });
  })
}

Any suggestions would be greatly appreciated!

Answer №1

In order to tackle this issue, my approach would involve creating a Class specifically designed to manage multiple "fancys".

To provide a complete solution, here is the response I shared in the comments yesterday - it covers all the requirements outlined in the question.

class Fancy {
    static fancies = {};
    static add(match_id, socket) {
        if (!Fancy.fancies[match_id]) {
            Fancy.fancies[match_id] = new Fancy(match_id);
        }
        Fancy.fancies[match_id].subscribe(socket);
    }

    subscribers = [];

    constructor(match_id) {
        this.match_id = match_id;
        this.checkInterval = setInterval(async () => {
            const fancy = await callapi(this.match_id);
            this.subscribers.forEach((socket) =>
                socket.emit(`fancy_${this.match_id}`, fancy)
            );
        });
    }

    subscribe(socket) {
        if (!this.subscribers.find((sock) => sock === socket)) {
            this.subscribers.push(socket);
        }
    }
}

socket.on('fancy' , ({match_id}) => Fancy.add(match_id, socket));

It's important to note that even though this is a class, only the static add method needs to be utilized.

However, it should be mentioned that this implementation does not account for disconnections or allowing users to opt-out of a "fancy" as indicated in the comment.

Expanding upon this code involves incorporating a few static methods and instance methods:

Introducing a static remove method which serves as the counterpart to the existing add method, and a disconnect static method to remove the socket from all instances.

For instance methods, including an unsubscribe, start, and stop method could enhance the functionality.

class Fancy {
    static fancies = {};
    static add(match_id, socket) {
        if (!Fancy.fancies[match_id]) {
            Fancy.fancies[match_id] = new Fancy(match_id);
        }
        Fancy.fancies[match_id].subscribe(socket);
    }

    static remove(match_id, socket) {
        Fancy.fancies[match_id]?.unsubscribe(socket);
    }

    static disconnect(socket) {
        Fancy.fancies.forEach(fancy => fancy.unsubscribe(socket));
    }

    // instance properties/methods

    subscribers = [];

    constructor(match_id) {
        this.match_id = match_id;
    }

    start() {
        if (!this.checkInterval) {
            this.checkInterval = setInterval(async () => {
                const fancy = await callapi(this.match_id);
                this.subscribers.forEach((socket) =>
                    socket.emit(`fancy_${this.match_id}`, fancy)
                );
            });
        }
    }

    stop() {
        clearInterval(this.setInterval);
        this.checkInterval = null;
    }

    unsubscribe(socket) {
        this.subscribers = this.subscribers.filter(sock => sock !== socket);
        if (this.subscribers.length === 0) {
            this.stop();
        }
    }

    subscribe(socket) {
        if (!this.subscribers.find((sock) => sock === socket)) {
            this.subscribers.push(socket);
            if (this.subscribers.length === 1) {
                this.start();
            }
        }
    }
}

socket.on('fancy' , ({match_id}) => Fancy.add(match_id, socket));
socket.on('unfancy' , ({match_id}) => Fancy.remove(match_id, socket));
socket.on('close', () => Fancy.disconnect(socket));

An alternative approach to the stop/start methods could involve keeping the interval running but only triggering the API request when there are active "subscribers".

This adjustment simplifies the code by adding just one instance method (unsubscribe).

Here is a modified version implementing this strategy:

class Fancy {
    static fancies = {};
    static add(match_id, socket) {
        if (!Fancy.fancies[match_id]) {
            Fancy.fancies[match_id] = new Fancy(match_id);
        }
        Fancy.fancies[match_id].subscribe(socket);
    }

    static remove(match_id, socket) {
        Fancy.fancies[match_id]?.unsubscribe(socket);
    }

    static disconnect(socket) {
        Fancy.fancies.forEach(fancy => fancy.unsubscribe(socket));
    }

    // instance properties/methods

    subscribers = [];

    constructor(match_id) {
        this.match_id = match_id;
        this.checkInterval = setInterval(async () => {
            if (this.subscribers.length) {
                const fancy = await callapi(this.match_id);
                this.subscribers.forEach((socket) =>
                    socket.emit(`fancy_${this.match_id}`, fancy)
                );
            }
        });
    }

    unsubscribe(socket) {
        this.subscribers = this.subscribers.filter(sock => sock !== socket);
    }

    subscribe(socket) {
        if (!this.subscribers.find((sock) => sock === socket)) {
            this.subscribers.push(socket);
        }
    }
}

Personally, I favor the last solution as having an interval that remains idle when there are no subscribers does not impose any performance issues.

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

Avoid automatically scrolling to the top of the page when a link is clicked

Within my ASP.NET MVC 5 application, there exists a partial view containing the following code: <a href="#" onclick="resendCode()" class="btn btn-link btn-sm">Resend</a> Additionally, on the View, the resendCode() function is present: funct ...

Using orchestrate.js for local passport.js authentication

I need assistance finding a tutorial or resources for setting up local passport.js authentication with Orchestrate as the user storage. Most of the resources I have come across are based on MongoDB, but our project requires us to use Orchestrate. Any advic ...

Ways to execute additional grunt tasks from a registered plugin

I am currently in the process of developing a custom Grunt plugin to streamline a frequently used build process. Normally, I find myself copying and pasting my GruntFile across different projects, but I believe creating a plugin would be more efficient. Th ...

How to Generate an Array of JSON Objects in JavaScript on a Razor Page using a Custom ViewModel in MVC?

Attempting to populate the array within my script for future charting with D3.JS, I came across some issues. Following advice from this post, I used a specific syntax that unfortunately resulted in an error stating "Uncaught ReferenceError: WebSite is not ...

Modify the Text Displayed in Static Date and Time Picker Material-UI

Looking to update the title text on the StaticDateTimePicker component? Check out this image for guidance. In the DOM, you'll find it as shown in this image. Referring to the API documentation, I learned that I need to work with components: Toolbar ...

What method is most effective for combining two JSON files in Angular?

My data includes a json file with a product list that looks like this: [{"id":76, "name":"A", "description":"abc", "price":199, "imageUrl":"image.jpg", "productCategory":[{ "categoryId":5, "category":null },{ "categoryId":6, " ...

When the property "a" is set to true, it must also require the properties "b" and "c" to be included

I'm looking for a way to modify the following type structure: type Foo = { a: boolean; b: string; c: string; } I want to change it so that if a is true, then both b and c fields are required. However, if a is false or undefined, then neither b ...

Using the Position Sticky feature in Next.js

As a newcomer to sticky elements, I decided to implement a sticky sidebar in my project. Unfortunately, after copying and pasting some code snippets related to sticky sidebars, it seems that the functionality is not working as expected. <Layout title= ...

Hide and show submenus with jQuery while ensuring that the initial submenu remains visible, along with the main menu

I am struggling to figure out how to make the first message active by default along with its corresponding menu selection in my navbar. I have implemented a show/hide functionality on the main menu click, but currently only the menu is being set as active ...

Error encountered when generating bower.json due to absence of version number

After generating the bower.json file, I noticed that the version number was not included in the information collected. The current content is as follows: { "Name": "conFusion", "Authors": [ "Aurora" ], ... } Although the version n ...

When I click the login button, a .ttf file is being downloaded to my computer

I have encountered an issue with my web application where custom font files in .ttf, .eot, and .otf formats are being downloaded to users' local machines when they try to log in as newly registered users. Despite attempting various solutions, I have b ...

Guidance on toggling elements visibility using Session Service in AngularJS

After reading this response, I attempted to create two directives that would control the visibility of elements for the user. angular.module('app.directives').directive('deny', ['SessionTool', function (SessionTool) { ret ...

Executing a function within another function (triggering the logout action by clicking the logout link) in ReactJS

Is there a way to access a method outside the render function and call it inside another function in order to log out the user with a simple click? I encountered the following error: Cannot read property 'AuthLogout' of undefined Let me shar ...

Implementing dual language codes for a single locale in internationalization (i18n) efforts

I am currently using i18n to display translations in English and Japanese. The default language is set to English with the code en, but I have recently discovered that my website is not utilizing the correct ISO language code for Japanese, which should be ...

Ways to extract particular items from a JSON array and store them in a JavaScript array

I am dealing with an external JSON-file structured as follows: { "type":"FeatureCollection", "totalFeatures":1, "features": [{ "type":"Feature", "id":"asdf", "geometry":null, "properties": { "PARAM1":"19 16 11", ...

Tips for automatically closing a dropdown menu when it loses focus

I'm having some trouble with my Tailwind CSS and jQuery setup. After trying a few different things, I can't seem to get it working quite right. In the code below, you'll see that I have placed a focusout event on the outer div containing th ...

How do you transform data stored in map to string format?

The main objective is to take a large txt file and replace all words according to the information in a csv file. This process will generate a new txt file as well as a new csv file that shows the frequency of each word. I'm currently struggling with ...

Using Angular to create a dynamic form with looping inputs that reactively responds to user

I need to implement reactive form validation for a form that has dynamic inputs created through looping data: This is what my form builder setup would be like : constructor(private formBuilder: FormBuilder) { this.userForm = this.formBuilder.group({ ...

Displaying the servlet response within an iframe

This is the content of Content.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> &l ...

Switch up the side navigation panel display

Hello, I am a newcomer to bootstrap and I have successfully implemented a collapsing navbar. However, I am looking for a way to make the navbar slide out from the right side when clicked on in mobile view. Can someone provide me with some JavaScript or j ...