Evaluating Javascript in browser with time limit Pub Share

Found myself working to implement some JavaScript fiddles and, of course, nobody else's was good enough... so it was easy enough to start: just eval()... but then what about mistakes and infinite loops?

Found this great idea using browser threads, aka HTML5 Web Workers: http://cwestblog.com/2016/03/16/javascript-detecting-infinite-loops/

However, it required a bit of improvement in catching exceptions inside the scripts as well as allow a bit of logging. So, without further ado, my slightly enhanced version is here:

// from http://cwestblog.com/2016/03/16/javascript-detecting-infinite-loops/
function limitEval(code, fnOnStop, opt_timeoutInMS, output) {
  var id = Math.random() + 1;

  var script = 'onmessage=function(a){' +
  'output="";'+
  'a=a.data;' +
  'postMessage({i:a.i+1});' +
  'var e="";' +
  'var x="";' +
  'try {' +
  '  x = eval.call(this, a.c);' +
  '} catch (ex) {' +
  '  console.log(ex); e = ex.toString();' +
  '};' +
  'postMessage({' +
  '  r:x,' +
  '  o:output,' +
  '  e:e,' +
  '  i:a.i' +
  '})};' +
  '';
  var blob = new Blob([script], { type:'text/javascript' } ),
      myWorker = new Worker(URL.createObjectURL(blob));

  function onDone() {
    URL.revokeObjectURL(blob);
    fnOnStop.apply(this, arguments);
  }

  myWorker.onmessage = function (data) {
    data = data.data;
    if (data) {
      if (data.i === id) {
        id = 0;
        if(typeof data.r != "undefined") {
          onDone(true, data.r,data.o);
          myWorker.terminate();
        } else if(data.e != "") {
          onDone(false, data.e);
          myWorker.terminate();
        } else {
          if(typeof data.o != "undefined" && data.o != "") {
            if(typeof output != "undefined") {
             output = output+data.o+"\n";
            }
          }
        }
      } else if (data.i === id + 1) {
        setTimeout(function() {
          if (id) {
            myWorker.terminate();
            onDone(false);
          }
        }, opt_timeoutInMS || 1000);
      }
    }
  };

  myWorker.postMessage({ c: 'var id='+id+';\n'+code, i: id });
}

And you can use it like so - on success or error, this will use some divs and iframes to show the results:

  limitEval(moreContext+preamble+"\n"+code, function(success, returnValue, outputStr) {
    var res = "";
    if (success) {
      res = returnValue;
      paintErr(false);
    }
    else {
      paintErr(true);
      res = returnValue || 'TIMEOUT - infinite loop?';
    }

    if(typeof res != 'undefined' && res != null) res = res.toString();
    else if(typeof res == 'undefined') res = "";
    var finalres = res;
    if(typeof outputStr != 'undefined' && outputStr != null) finalres = "<b>"+res + "</b>" + "\n--------------\n"+outputStr;
    if(code.length > 0) $('#iframe_'+id).html(finalres);
    else $('#iframe_'+id).text("Start typing...");
  }, 3000);

I'll post up the rest of the fiddle code - you may find it interesting.

Update - handling old results

In case the expression is really slow, let's say a mean for(;;), what do we do?

The callback is a closure, so all you need is a evaluation counter that you maintain and just compare the values... like so:

var seqNum_111 = 0;  // counter

var runpill_111 = function(id) {
   // ...
   seqNum_111 = seqNum_111 +1;
   var closureSeq = seqNum_111;

  limitEval(..., function(success, returnValue, outputStr) {
    if(closureSeq != seqNum_111) {
      console.log("ignored old result: "+closureSeq+' current: '+seqNum_111);
      return;
    }

Now - if someone keeps typing and you didn't debounce it smartly, it will just delay displaying a result instead of seeing possibly inconsistent results, randomly.

You need the closureSeq inside the runpill, to capture the value when the execution occured... this is a simple example, in real life you'd likely have it all be callbacks, using 'onchange' or some other notification - check the code behind the next post to see one implementation.


Was this useful?    

By: Razie | 2016-10-17 .. 2016-10-18 | Tags: post , javascript


See more in: Cool Scala Subscribe

Viewed 807 times ( | History | Print ) this page.

You need to log in to post a comment!