Search code examples
javascriptcallbacksetintervalrevealing-module-pattern

Timer (callback) inside a revealing module to update public property


Im attempting to get a callback working inside a module in order to update a property. In this case I just want it to toggle the property value, and would like to be able to see that externally.

Here is the simple module

var Module = (function () {


    // private
    var isLoaded=false;
    var timer = null;

    var _privateMethod = function () {
        console.log('hello from private')
    }

    //public
    var publicMethod = function(){
        console.log('in public method')
        _privateMethod();
    }

    var start = function(){
        console.log('starting timer')
        timer = setInterval( function() {
            console.log('timer callback')
            isLoaded = !isLoaded;
        },1000)
    }

    var stop = function(){
        console.log('stopping timer')
        clearInterval(timer)
    }

    return {
        publicMethod: publicMethod,
        isLoaded: isLoaded,
        start: start,
        stop: stop,
    }

})();

module.exports = Module;

and im testing it with this

function sleep(milliseconds) {
    var start = new Date().getTime();
    for (var i = 0; i < 1e7; i++) {
      if ((new Date().getTime() - start) > milliseconds){
        break;
      }
    }
  }

var DM = require('./module.js')

DM.publicMethod()
DM.start()

console.log(DM.isLoaded)
 sleep(500)

console.log(DM.isLoaded)
 sleep(500)

console.log(DM.isLoaded)
 sleep(500)

console.log(DM.isLoaded)
 sleep(500)

 DM.stop();

It doesnt toss an error, and it looks like its starting the timer ok but im never getting the output from the callback and the property isnt updating. Im guessing its a scope issues, but I cant put my finger on it.


Solution

  • To do this (with that general structure), you have two options:

    Keep the object

    Keep a reference to the object you return:

    var obj;
    
    // ...
    
    obj = {
        publicMethod: publicMethod,
        isLoaded: false,
        start: start,
        stop: stop,
    };
    return obj;
    

    ...and remove the isLoaded variable, and instead set obj.isLoaded = true in the callback.

    Live example:

    var Module = (function() {
        var obj;
        var timer = null;
    
        var start = function(){
            console.log('starting timer')
            timer = setInterval( function() {
                console.log('timer callback')
                obj.isLoaded = !obj.isLoaded;
            },1000)
        };
    
        var stop = function(){
            console.log('stopping timer')
            clearInterval(timer)
        };
        
        obj = {
            //publicMethod: publicMethod,
            isLoaded: false,
            start: start,
            stop: stop,
        };
        return obj;
    })();
    Module.start();
    
    var t = setInterval(function() {
      console.log("Module.isLoaded = " + Module.isLoaded);
    }, 500);
    setTimeout(function() {
      console.log("Stopping");
      clearInterval(t);
      Module.stop();
    }, 10000);
    .as-console-wrapper {
      max-height: 100% !important;
    }

    Use an accessor property:

    This has the advantage of being read-only:

    return {
        publicMethod: publicMethod,
        get isLoaded() {
            return isLoaded;
        },
        start: start,
        stop: stop,
    };
    

    Live example:

    var Module = (function() {
        var isLoaded = false;
        var timer = null;
    
        var start = function(){
            console.log('starting timer')
            timer = setInterval( function() {
                console.log('timer callback')
                isLoaded = !isLoaded;
            },1000)
        };
    
        var stop = function(){
            console.log('stopping timer')
            clearInterval(timer)
        };
        
        obj = {
            //publicMethod: publicMethod,
            get isLoaded() {
                return isLoaded;
            },
            start: start,
            stop: stop,
        };
        return obj;
    })();
    Module.start();
    
    var t = setInterval(function() {
      console.log("Module.isLoaded = " + Module.isLoaded);
    }, 500);
    setTimeout(function() {
      console.log("Stopping");
      clearInterval(t);
      Module.stop();
    }, 10000);
    .as-console-wrapper {
      max-height: 100% !important;
    }