Calling javascript which uses setTimeout/setInterval

May 8, 2014 at 12:59 PM
For one of the projects, I use some pre-existing java script files and expose them via C# to use in some C# libraries.

I am encountering errors while trying to execute java scripts files which uses async calls via setTimeout/setInterval.

Specific error is "ReferenceError: setTimeout is not defined".

Any suggestions or workaround on how I can execute such java script files?

Thanks!!!
Coordinator
May 8, 2014 at 8:42 PM
Greetings!

ClearScript provides a bare JavaScript environment, whereas the functions you're looking for are part of the Web API, which is typically provided by web browsers.

The functions are relatively easy to implement, but because they provide asynchronous operations, the implementation may depend on your application's threading model.

Here's an example that uses .NET timers under the covers and should work in typical multithreaded .NET applications. First, a simple .NET class that provides timer access and can route callbacks to script code:
public static class ScriptTimer {
    public static object Create(dynamic func, double delay, double period, object args) {
        var callback = new TimerCallback(state => func.apply(null, args));
        return new Timer(callback, null, Convert.ToInt64(delay), Convert.ToInt64(period));
    }
}
Next, script functions that use the class to provide the required behavior:
engine.AddHostType("ScriptTimer", typeof(ScriptTimer));
engine.Execute(@"
    function createTimer(periodic, func, delay) {
        var period = periodic ? delay : -1;
        var args = Array.prototype.slice.call(arguments, 3);
        return ScriptTimer.Create(func, delay, period, args);
    }
    setTimeout = createTimer.bind(null, false);
    setInterval = createTimer.bind(null, true);
    clearTimeout = function(id) { id.Dispose(); };
    clearInterval = clearTimeout;
");
Again, this implementation may not be right for your application, but hopefully it gives you an idea of how .NET and script code can work together to provide whatever functionality you need.

Good luck!
Feb 23, 2015 at 7:49 AM
Hello,
I was able to get this working with a basic example. Then I tried to tweak it to suit my particular needs but I am not having any luck. Technically, I don't specifically need to have the setTimeout function. Instead, any function will do the trick that provides a delayed callback into the script. Also, I don't need the periodic feature as long as I can repeate the delayed function a few times. In my application, the user will be creating counters and when the counter gets to say 5 or 10, they will stop counting until some other event fires to start the counting again. This can be done either with the periodic function or with a delay where the called back function calls the delay again each time until the target value is reached. I prefer the delay in case the user messes up and does not stop the periodic timer - it just seems a little safer.


The trick for me is that I want to do this in a JS class that will be instantiated a hundred times. Each class has its own timer that it uses for counting against its instance data. I tried using setTimeout based upon the solution above and passed this.Run as the callback function. It seemed to me that this lost its context because setTimeout was created in global scope. I'm not all that solid with JavaScript classes and context so I'm not really sure why this didn't work. At one point through my attempted tweaks, I called this.Run on one instance and it ran on all instances. My code looks something like this.
var DVC = function(tag) {
  this.Tag = tag;
  this.timerTarget = 5;
  this.count = 0;
  var _start = false;
  Object.defineProperty(this, 'start', {
    get: function() { return _start; },
    set: function(value) {
             this._start = value;
             if (value === true) { setTimeout(this.Run, 1000);}
           }
  });

  this.Run = function() {
    if (this.count >= this.timerTarget) {
      //do something ...
    }else{
      this.count++;
      setTimeout(this.Run, 1000);
    }
  };
};

var x = new DVC("YV-101");
var y = new DVC("YV-201");

function calledByUser() {
  x.start = true;
}

function someOtherFunction(){
  y.start = true;
}
Any suggestions?
Coordinator
Feb 24, 2015 at 3:14 AM
Hello!

The problem seems to be that you aren't binding your Run function to a DVC instance. Consider defining it as follows:
var DVC = function(tag) {
  //...
  this.Run = this.RunMethod.bind(this);
};
DVC.prototype.RunMethod = function() {
  //...
};
Good luck!
Feb 27, 2015 at 6:04 PM
That did the trick. I understand the bind method in an abstract sense but without an understanding of how JS handles and changes context its difficult to know when to use it. Your answer prompted me to do a little testing to observe when and how the context changes so I think I understand it better. I assumed that calling a class instance method should have the instance as the current context but in fact it appears to pass the context of the caller by default. I was calling the instance method from the global scope in some cases and from within another object instance in other cases. The bind method fixed those problems.

I know this is technically not really a ClearScript issue - its a lack of understanding JavaScript on my part so I am doubly thankful for your help. My classes are awesome now.

Thanks again!