1

im trying to do something similar to google docs. It's an autosave system/loop. I'll save a document after a few seconds of inactivity (by calling a save method from the class).

If there is some activity during that few seconds, then I will reset/prolong the timer by another few seconds. This cycle will continue until the save method is being called, of which i will break the loop and wait for another activity to start the loop again.

Here's some pseudo code i came up with:

doc.on('mouse:up', (event) => {
        ... //<--- Initialize the timer on mouse:up event
        ... //<--- When time is up, execute the save method
    })

doc.on('mouse:down', (event) => {
        ... //<--- prolong timer initialized in the mouse:up event if it is available.
        }

However, i don't think this way of doing is possible as I cant access the same initialized setTimeOut object. Is there a good way to implement this? would love to hear from yall.

neowenshun
  • 860
  • 1
  • 7
  • 21
  • Why can't you access the same object? You can if the variable is scoped higher than each function. – Taplar Dec 28 '20 at 17:58
  • 2
    Store the result of `setTimeout` in a global variable and call `clearTimeout` with that variable (which is the timer id) in another method. This is basically the same as a `debouncer` https://davidwalsh.name/javascript-debounce-function (and here: https://stackoverflow.com/questions/24004791/can-someone-explain-the-debounce-function-in-javascript) – somethinghere Dec 28 '20 at 17:58
  • Hm, I can can move the timer object up a level and wrap it in a lambda to prevent it from initializing unless it's a mouse up event, but in the end it will still have to be done within the scope of the mouse:up function – neowenshun Dec 28 '20 at 18:01
  • @neowenshun Check out the links to the debouncer, it wraps the whole thing in it's own scope neatly and it's been around for years, tried and tested. The difficulty is abstracting it away for multiple input events, but you could also just run an interval timer to save and a flag to actually perform it that you reset after save. – somethinghere Dec 28 '20 at 18:03
  • Does this answer your question? [What is the scope of variables in JavaScript?](https://stackoverflow.com/questions/500431/what-is-the-scope-of-variables-in-javascript) – Taplar Dec 28 '20 at 18:04

3 Answers3

2

Simply initialize the variable outside of both functions. This way, it will be accessible inside either of them :

let theTimeout = null;

doc.on('mouse:up', (event) => {
        theTimeout = setTimeout(...); //<--- Initialize the timer on mouse:up event
    })

doc.on('mouse:down', (event) => {
        clearTimeout(theTimeout);
        theTimeout = setTimeout(...); //<--- prolong timer initialized in the mouse:up event if it is available.
}
Jeremy Thille
  • 26,047
  • 12
  • 43
  • 63
2

There are a few ways to handle this.

Approach: Basic
Cons: Low level, behavior is not as immediately obvious.

const saveDelayMs = 1000;
let timeoutId;

doc.on('mouse:up', (event) => {
   timeoutId = setTimeout(save, saveDelayMs);
});

doc.on('mouse:down', (event) => {
   clearTimeout(timeoutId);
   timeoutId = setTimeout(save, saveDelayMs);
}

Approach: Debounce Abstraction
Pros: Higher level, code more readable

// Trigger 1000ms after last call
const debouncedSave = debounce(save, 1000);

doc.on('mouse:up', (event) => {
   debouncedSave();
});

doc.on('mouse:down', (event) => {
   debouncedSave();
});

function debounce(fn, debounceDelayMs) {
   let timeoutId;
  
   return () => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(fn, debounceDelayMs);
   };
});

Lodash provides a debounce function, which I recommend over creating your own.

Adam Arthur
  • 409
  • 3
  • 10
1

You could use a debouncing method (see here: https://davidwalsh.name/javascript-debounce-function), but probably the clearest if you are going to look at multiple types of inputs (say, dragging an object stops saving, but also touch, and also pointers etc..), it's best to use a global flag and use it to toggle the save flag from anywhere in your code, so anybody can trigger the suggestion for saving:

let saveFlag = false;

setInterval(function(){
   
   if( saveFlag === true ){
      
      console.log( 'save' );
      saveFlag = false;
    
   } else {
   
      console.log( 'skip save' );
      
   }
  
}, 3000);

// Now you can trigger a save on any kind of event,
// and expect it to happen within 3 seconds.

document.addEventListener( 'click', e => saveFlag = true);
document.addEventListener( 'touchend', e => saveFlag = true);
document.addEventListener( 'pointerup', e => saveFlag = true);

This will also prevents duplicate timers from potentially being created, and has a maximum delay or 3000ms in my example, with very little overhead.

somethinghere
  • 16,311
  • 2
  • 28
  • 42
  • Hm, so in this case, the interval function is always running? Im pretty much a beginner, so im curious if there's any performance issue with this – neowenshun Dec 28 '20 at 18:20
  • The interval itself won’t cause a performance bottleneck. I’d be more worried that constantly starting and stopping timers has more overhead involved, and managing that code will be rather complicated for all the event listeners that could debounce. Timers are such a classic, basic js tool since the beginning, they’re very optimised, I have always heard that concern you talk about, but I never noticed anything like that in practise at all. – somethinghere Dec 28 '20 at 18:28
  • 1
    Thank you for the detailed response! think in my case however a debouncer seems to be a pretty good fit (i had no idea such a thing existed) Appreciate the help man – neowenshun Dec 28 '20 at 18:44