2

Given I have the following configuration:

let config = {
  "requests": [
    {
      "resource": "foo",
      "interval": 1000
    },
    {
      "resource": "bar",
      "interval": 500
    },
    {
      "resource": "baz",
      "interval": 3000
    },
    {
      "resource": "qux",
      "interval": 500
    },
    {
      "resource": "zot",
      "interval": 500
    },
  ],
  // other configs...
}

I need to make a recursive setTimeout calls where I check what resources should be requested from a server at a given call and make the request to the server.

For example, considering the array above:

After 500ms, since it's the smallest interval, I have to make a request and pass array of ['bar', 'qux', 'zot']. After another 500ms, and since it's already 1000ms, the array should be ['bar', 'qux', 'zot', 'foo']. After another 500 it should be again ['bar', 'qux', 'zot']. When reaches 3000 - ['bar', 'qux', 'zot', 'foo', 'baz'], and so on...

The configuration itself is coming from a server when the app starts and it's a black box to me. Meaning I can't know exactly what intervals may be configured and how many of them are there. Here I made them increase by 500ms for convenience only (though I think I might make it a technical requirement to the back-end guys, lets assume I can't).

I'm not sure how to tackle this problem. I thought maybe I should store an array of required intervals and do something with that array. Perhaps store a current request as an object with timestamp and interval. Something like that:

const { requests } = config
let intervals = new Set()
for (let obj of requests) {
  intervals.add(obj.interval)
}
intervals = Array.from(intervals).sort((a, b) => a - b) //[500, 1000, 3000]

let currentRequest

const makeRequest = resources => {
  // send request to server with given resources array
  // init the next request by calling initRequest again
}

const initRequest = () => {
  const interval = 500 // just for example, the actual interval should be determind here
  const resources = [] // Here I should determine what should be the requested resources
  setTimeout(() => {
    makeRequest(resources)
  }, interval)
}

initRequest()

But I'm not sure about the logic here. How can this be done? Any ideas, please?

Igal
  • 5,833
  • 20
  • 74
  • 132
  • @AndrewParks Frankly I'm not sure, the spec didn't mention it. I believe it should be after the first interval, since I'm not sure how to determine what resources to retrieve at time 0. – Igal Nov 04 '22 at 15:04

2 Answers2

2

We create an upcoming array, which stores the next timestamp at which each resource should be retrieved. Then, we know that we need to set a timeout that triggers at the time of the next upcoming timestamp.

When the timeout triggers, we execute upcoming items that are scheduled to run right now. We then calculate the next time it should be run.

Finally, we set a new timeout to coincide with the next time a task is scheduled to run.

const config = {
  requests: [
    { resource: 'foo', interval: 1000 },
    { resource: 'bar', interval: 500 },
    { resource: 'baz', interval: 3000 },
    { resource: 'qux', interval: 500 },
    { resource: 'zot', interval: 500 }
  ]
};
let fireAllAtTimeZero = false;
let t = 0;
let upcoming = config.requests.map(
  i=>({...i, next: fireAllAtTimeZero ? 0 : i.interval}));
f = () => {
  let now = upcoming.filter(i=>i.next===t);
  console.log(`time = ${t}, doing: ${now.map(i=>i.resource).join()}`);
  // initiate your async request here for every item in 'now' array
  now.forEach(i=>i.next = t + i.interval);
  let next = upcoming.map(i=>i.next).sort((a,b)=>a-b)[0];
  let delay = next-t;
  t = next;
  setTimeout(f, delay);
};
f();
Andrew Parks
  • 6,358
  • 2
  • 12
  • 27
  • 1
    I think this exactly what I needed! Checked it with several scenarios (different intervals) and looks like it works as desired. Thank you so much! – Igal Nov 04 '22 at 15:11
0

You could use setTimeouts:

initRequests = () => {
  let i = -1;
  const resources = [];
  const timeoutHandler = () => {
    if(i < intervals.length){
      setTimeout(timeoutHandler, intervals[++i]);
    }
  }
  timeoutHandler();
}

Dr. Vortex
  • 505
  • 1
  • 3
  • 16
  • Thanks for the reply! Unfortunately this solution it doesn't solve my issue. First of all sets only 3 timeouts - with 500, 1000 and 3000ms and stops there, while in this case I need the request to be at 500, 1000, 1500 ms and so on. And what if in the config I receive, for example, only 2 requests, with 300ms and 800ms? Then it has to be timeouts with 300, 600, 800, 900. 1200, 1500, 1600 and so on delays. Basically I need to somehow calculate what is the interval until next timeout and what are the resources that should be requested. And it has to happen recursively. – Igal Nov 04 '22 at 14:45
  • @Igal why not use similar approach as this however with `setIntervals`? – Giorgi Moniava Nov 04 '22 at 15:06
  • @GiorgiMoniava Basically there's a whole debate about setInterval vs recursive setTimeout (here, for example: https://stackoverflow.com/questions/23178015/recursive-function-vs-setinterval-vs-settimeout-javascript). For me (and my lead architect) the main cons are that setInterval doesn't guarantee the delay before executions, setTimeout - does. Another drawback of setInterval (and I'm quoting an answer from the above mentioned topic): The callback will be triggered even if an uncaught exception was thrown. There are a few more advantages to use recursive setTimeout over setInterval. – Igal Nov 04 '22 at 15:19