0

Just stumbled across a comment in this very old question regarding clearing all setTimeouts.

The comment has intrigued me as it doesn't actually address the question directly, but offers a really interesting alternative solution to the problem of keeping track of multiple timers, but sadly he offers no example of how this could be implemented :

Use a global timeout which all of your other functions derive timing from. This will make everything run faster, and be easier to manage, although it will add some abstraction to your code...

... I just meant that you have one global timeout running once every 50ms. Every other function which would require a timing element would then grab it from the global timeout. Google switched to this for efficiency, although I can no longer find the article quoting it.

How would one go about setting what I assume is a named timer in the global space and then referencing it elsewhere in multiple cases?

For example if we had something like this in the global space :

let myGlobalTimer = setTimeout(function(){ myFunc(); }, 50);

That would only run myFunc every 50ms.

I'm pretty sure you can't pass dynamic function names into setTimeout so how would one achieve this?

spice
  • 1,442
  • 19
  • 35
  • 1
    Reading some of the answers and following some of the links leads to this scheduler: [*Avoiding JavaScript setTimeout and setInterval Problems*](https://www.onsip.com/blog/avoiding-javascript-settimeout-and-setinterval-problems), which is a little more sophisticated than the answer below (though it misuses the term "context" when it means "this"). – RobG Oct 01 '18 at 03:01

1 Answers1

2

You can have the timeout run through an array or Set of functions every time the timeout is triggered. To add a function that gets run with every iteration, add it to the Set; to stop it from running, remove it from the Set. For example:

const fns = new Set();
function runAllTasks() {
  for (const fn of fns) {
    fn();
  }
  // Run every 500 ms:
  setTimeout(runAllTasks, 500);
}

fns.add(() => console.log('fn one running'));
const fn2 = () => console.log('fn two running');
fns.add(fn2);
runAllTasks();

// example, remove fn2 from the Set after 1300ms:
setTimeout(() => {
  fns.delete(fn2);
}, 1300);

Of course, you could use an array too, but a Set is more appropriate when the order of items in the collection doesn't matter.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Wow that's pretty impressive! So doing it this way there will only ever be 1 setTimeout running no matter how many functions you add to the Set? – spice Oct 01 '18 at 02:04
  • Any chance you could roll a version of this in vanilla JS instead of ES2015 please? I just ran it through the babel decompiler and it looked insane lol. https://pastebin.com/5CZ5tUjN – spice Oct 01 '18 at 02:08
  • How would this solution work if the functions have different timeouts? E.g. function 1 has to run after 500 ms but function 2 has to run after 1700 ms? Also it runs the same functions over and over again instead of one-and-done? Also you're making a 2nd timeout to delete a function from the set which seems to go against the constraint of the question which is to use a single global timeout. – pretzelhammer Oct 01 '18 at 02:10
  • @kfedorov91 that's not an issue for my particular use case. All of the timers I'm running for this particular aspect of the project all run in 500ms intervals. So this solution is perfect for me. I assume the delete function could be called directly without a timeout right? – spice Oct 01 '18 at 02:11
  • 1
    @spice If you don't want to use `for..of`, you can use `forEach` instead. `fns.forEach(fn => fn())` – CertainPerformance Oct 01 '18 at 02:14
  • @spice if you don't need variable timeouts per callback why not just use setInterval() then? – pretzelhammer Oct 01 '18 at 02:17
  • @kfedorov91 it's mainly used for updating the time display (and keeping the time) on audio playback. setTimeout is a lot more consistent than setInterval for this. – spice Oct 01 '18 at 02:20
  • 1
    @spice this ES2015 is Vanilla JS – vol7ron Oct 01 '18 at 02:51
  • 1
    @spice—only if you set the delay for each *setTimeout* call based on the time remaining to the next 500ms tick after all the functions have completed. This answer will set the delay to 500ms after the last function completes, which will not give even 500ms delays. – RobG Oct 01 '18 at 02:53
  • @vol7ron What I meant is that it's using ES6. I'm writing this particular project in straight up oldskool JS (no fat arrows) so I'm not using webpack or any other compiler. I set myself a mission to learn more about straight up vanilla JS with this project, I'm not even using jQuery anywhere for this one. Wanted to give myself a better knowledge of what's under the hood which is why I opted for the long-winded approach :D – spice Oct 01 '18 at 05:12
  • Then you’re still learning a certain version of JS. You ought to first learn which version you’re trying to support. Each version has its notable differences, especially in the different browsers – vol7ron Oct 01 '18 at 10:50