3

I am trying to make an API call and I want it to repeat every 2 seconds. However I am afraid that if the system doesn't get a request back in 2 seconds, that it will build up requests and keep trying to send them. How can I prevent this?

Here is the action I am trying to fetch:

const getMachineAction = async () => {
    try {
        const response = await fetch( 'https://localhost:55620/api/machine/');
        if (response.status === 200) {
            console.log("Machine successfully found.");
            const myJson = await response.json(); //extract JSON from the http response
            console.log(myJson);               
        } else {
            console.log("not a 200");
        }
    } catch (err) {
        // catches errors both in fetch and response.json
        console.log(err);
    }
};

And then I call it with a setInterval.

function ping() {
    setInterval(
        getMachineAction(),
        2000
    );        
}

I have thought of doing some promise like structure in the setInterval to make sure that the fetch had worked and completed, but couldn't get it working.

VLAZ
  • 26,331
  • 9
  • 49
  • 67
thalacker
  • 2,389
  • 3
  • 23
  • 44
  • 1
    Is every 2 seconds an exact requirement? If not, don't do it every 2 seconds. Use `setTimeout` and schedule the next 2 seconds after the previous finishes instead. – zero298 Apr 04 '19 at 18:02
  • There will be a set time, whether it is 2 seconds, or 30 seconds will depend on a variable, but it will stay consistent. (so if it is set to every 2 seconds, then it needs to happen every 2 seconds) – thalacker Apr 04 '19 at 18:08
  • 1
    Maybe look into [throttling/debouncing](https://codeburst.io/throttling-and-debouncing-in-javascript-b01cad5c8edf) - I forget which is which. lodash has both methods. – Andy Apr 04 '19 at 18:08
  • 1
    What do you want to happen to the previous request at the 2 second mark? Should it cancel? What is your "bad case" requirement? – zero298 Apr 04 '19 at 18:19
  • @zero298 the "bad case" (i.e. it takes longer than 2 seconds) is I want it to skip that request, and then send a single new one. So at 0 seconds the request sends. It takes 3 seconds to execute, then 2 seconds later (at 5) it should reexcute. So it just extends the time until it sends. – thalacker Apr 04 '19 at 18:52
  • @Andy Neither of these will help here – Bergi Apr 04 '19 at 19:03
  • @Bergi, I agree. do you have any recommendations? I am inclined to think charlietfl 's answer is the way to go. – thalacker Apr 04 '19 at 19:08
  • 1
    @thalacker takrishna's approach is the way to go. See also [here](https://stackoverflow.com/a/33292942/1048572) – Bergi Apr 04 '19 at 19:21

3 Answers3

6

The Promise.all() Solution

This solution ensures that you don't miss-out on 2 sec delay requirement AND also don't fire a call when another network call is underway.

function callme(){
//This promise will resolve when the network call succeeds
//Feel free to make a REST fetch using promises and assign it to networkPromise
var networkPromise = fetch('https://jsonplaceholder.typicode.com/todos/1');


//This promise will resolve when 2 seconds have passed
var timeOutPromise = new Promise(function(resolve, reject) {
  // 2 Second delay
  setTimeout(resolve, 2000, 'Timeout Done');
});

Promise.all(
[networkPromise, timeOutPromise]).then(function(values) {
  console.log("Atleast 2 secs + TTL (Network/server)");
  //Repeat
  callme();
});
}
callme();

Note: This takes care of the bad case definition as requested by the author of the question:

"the "bad case" (i.e. it takes longer than 2 seconds) is I want it to skip that request, and then send a single new one. So at 0 seconds the request sends. It takes 3 seconds to execute, then 2 seconds later (at 5) it should reexcute. So it just extends the time until it sends."

takrishna
  • 4,884
  • 3
  • 18
  • 35
5

You could add a finally to your try/catch with a setTimeout instead of using your setInterval.

Note that long polling like this creates lot more server load than using websockets which themselves are a lot more real time

const getMachineAction = async () => {
    try {
        const response = await fetch( 'https://localhost:55620/api/machine/');
        if (response.status === 200) {
            console.log("Machine successfully found.");
            const myJson = await response.json(); //extract JSON from the http response
            console.log(myJson);               
        } else {
            console.log("not a 200");
        }
    } catch (err) {
        // catches errors both in fetch and response.json
        console.log(err);
    } finally {
        // do it again in 2 seconds
        setTimeout(getMachineAction , 2000);
    }
};

getMachineAction()
charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • 2
    Note that this makes it happen 2000 seconds after the request has finished, not 2000 seconds after the request was started. – wizzwizz4 Apr 04 '19 at 18:15
  • @charlietfl do you have a websockets resource you would recommend checking out or reading about? – thalacker Apr 04 '19 at 18:53
  • What language are you running in back end? Just do a search websockets. If it is a node server check out socket.io – charlietfl Apr 04 '19 at 18:56
  • @charlietfl I am running a C#, .NET Core Web API. I can search for C# web sockets. Primarily connecting to it via external devices to collect data – thalacker Apr 04 '19 at 19:00
0

Simple! Just store whether it's currently making a request, and store whether the timer has tripped without sending a new request.

let in_progress = false;
let missed_request = false;
const getMachineAction = async () => {
    if (in_progress) {
        missed_request = true;
        return;
    }
    in_progress = true;
    try {
        const response = await fetch('https://localhost:55620/api/machine/');
        if (missed_request) {
            missed_request = false;
            setTimeout(getMachineAction, 0);
        }
        if (response.status === 200) {
            console.log("Machine successfully found.");
            const myJson = await response.json(); //extract JSON from the http response
            console.log(myJson);               
        } else {
            console.log("not a 200");
        }
    } catch (err) {
        // catches errors both in fetch and response.json
        console.log(err);
    } finally {
        in_progress = false;
    }
};

To start the interval, you need to omit the ():

setInterval(getMachineAction, 2000); 
wizzwizz4
  • 6,140
  • 2
  • 26
  • 62
  • Hi @wizzwizz4, this would likely work if I was okay skipping requests, (i.e. execute at 2,4,8,10 and it had skipped 6), but I want it to work like it will execute asap and then go 2 seconds after that (i.e. execute at 2,4,7,9,10 where the 6 took an extra second so now its 2 seconds after). Also, I think you need to set your `in_progress` variable to true in the first line after the `try`, right? Thanks for taking the time to answer! – thalacker Apr 04 '19 at 19:07
  • @thalacker Yes, I do. (Or first line before, whatever works.) And this should do 2, 4, 6.3, 8, 10 if the one that starts on 4 takes 2.3 seconds, unlike charlietfi's solution. The `Promise.all` solution is neat, but seems to function almost identically to chatlietfi's solution (2, 4, 6.3, 8.3, 10.3). Which functionality were you looking for? – wizzwizz4 Apr 05 '19 at 00:44