Question

Summary: How can I execute a JavaScript function, but then "execute" (kill) it if it does not finish with a timeframe (e.g. 2 seconds)?

Details

I'm writing a web application for interactively writing and testing PEG grammars. Unfortunately, the JavaScript library I'm using for parsing using a PEG has a 'bug' where certain poorly-written or unfinished grammars cause infinite execution (not even detected by some browsers). You can be happily typing along, working on your grammar, when suddenly the browser locks up and you lose all your hard work.

Right now my code is (very simplified):

grammarTextarea.onchange = generateParserAndThenParseInputAndThenUpdateThePage;

I'd like to change it to something like:

grammarTextarea.onchange = function(){
  var result = runTimeLimited( generateParserAndThenParseInput, 2000 );
  if (result) updateThePage();
};

I've considered using an iframe or other tab/window to execute the content, but even this messy solution is not guaranteed to work in the latest versions of major browsers. However, I'm happy to accept a solution that works only in latest versions of Safari, Chrome, and Firefox.

Était-ce utile?

La solution

Web workers provide this capability—as long as the long-running function does not require access to the window or document or closures—albeit in a somewhat-cumbersome manner. Here's the solution I ended up with:

main.js

var worker, activeMsgs, userTypingTimeout, deathRowTimer;
killWorker(); // Also creates the first one

grammarTextarea.onchange = grammarTextarea.oninput = function(){
  // Wait until the user has not typed for 500ms before parsing
  clearTimeout(userTypingTimeout);
  userTypingTimeout = setTimeout(askWorkerToParse,500);
}

function askWorkerToParse(){
  worker.postMessage({action:'parseInput',input:grammarTextarea.value});
  activeMsgs++;                                // Another message is in flight
  clearTimeout(deathRowTimer);                 // Restart the timer
  deathRowTimer = setTimeout(killWorker,2000); // It must finish quickly
};

function killWorker(){
  if (worker) worker.terminate();   // This kills the thread
  worker = new Worker('worker.js')  // Create a new worker thread
  activeMsgs = 0;                   // No messages are pending on this new one
  worker.addEventListener('message',handleWorkerResponse,false);
}

function handleWorkerResponse(evt){
  // If this is the last message, it responded in time: it gets to live.
  if (--activeMsgs==0) clearTimeout(deathRowTimer);
  // **Process the evt.data.results from the worker**
},false);

worker.js

importScripts('utils.js') // Each worker is a blank slate; must load libs

self.addEventListener('message',function(evt){
  var data = evt.data;
  switch(data.action){
    case 'parseInput':
      // Actually do the work (which sometimes goes bad and locks up)
      var parseResults = parse(data.input);

      // Send the results back to the main thread.
      self.postMessage({kind:'parse-results',results:parseResults});
    break;
  }
},false);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top