7

the following test is basically ~1000 math operations and works fine on most PC and android browsers, and iOS 4.x. On iOS5 safari (iPhone 4 and iPad 2) we get "JavaScript: Error undefined JavaScript execution exceeded timeout". Any help greatly appreciated thanks.

/** Converts numeric degrees to radians */
if (typeof (Number.prototype.toRad) === "undefined") {
    Number.prototype.toRad = function () {
    return this * Math.PI / 180;
    }
}

function gc(lat1, lon1, lat2, lon2) {
    // returns the distance in km between a pair of latitude and longitudes
    var R = 6371; // km
    var dLat = (lat2 - lat1).toRad();
    var dLon = (lon2 - lon1).toRad();
    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
        Math.sin(dLon / 2) * Math.sin(dLon / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = R * c;
    return d;
}

function test() {
    var d1 = new Date();
    var lat1, lon1, lat2, lon2;
    lat1 = -36;
    lon1 = 174;

    lat2 = lat1;
    lon2 = lon1;

    while (lat2 > -37) {
    lat2 = lat2 - 0.001;
    var stest = "lat1=" + lat1 + ",lon1=" + lon1 + ",lat2=" + lat2 + ",lon2=" + lon2 + "=" + gc(lat1, lon1, lat2, lon2);

    }
    var d2 = new Date();
    var stest = (d2.getTime() - d1.getTime()) / 1000.0 + "s";
    $("#lblTest").html(stest + "<BR/>" + $("#lblTest").html());

}
ajayel
  • 3,027
  • 2
  • 26
  • 33

4 Answers4

11

If you want to execute a long running operation in javascript and you are getting anywhere close to the script execution time limit enforced by some browsers, then you will have to break your function into multiple pieces, run one piece, to a very short setTimeout(fn, 1) and then execute the next piece, etc... Doing it this way, you can run code for hours because it gives other scripts and other events a chance to process. It sometimes requires a minor amount of code restructuring to be able to do this, but it's always possible with a little work.

The basic concept in pseudo-code would be this:

var state = {};   // set initial state
var done = false;

function doWork() {
   // do one increment of work that will never get even close to the browser
   // execution time limit
   // update the state object with our current operating state for the next execution
   // set done = true when we're done processing
   if (!done) {
       setTimeout(doWork, 1);
   }
}

doWork();

In your specific code, you could do something like this. You can process 100 latitude points at a time and then do a short setTimeout to do the next 100 and so on. You can adjust that 100 number to whatever would work best. The higher the number, the more you do on each timer and the better overall execution time, but the closer you get to the browser script execution limit. The setTimeout keeps the browser alive (processing other events) and prevents the execution time limit from kicking in.

function test() {
    var d1 = new Date();
    var lat1, lon1, lat2, lon2, done = false;;
    lat1 = -36;
    lon1 = 174;

    lat2 = lat1;
    lon2 = lon1;

    function calcGC() {
        var cntr = 0;
        while (lat2 > -37 && cntr < 100) {
            lat2 = lat2 - 0.001;
            var stest = "lat1=" + lat1 + ",lon1=" + lon1 + ",lat2=" + lat2 + ",lon2=" + lon2 + "=" + gc(lat1, lon1, lat2, lon2);
            cntr++;
        }
        // if we have more to go, then call it again on a timeout
        if (lat2 > -37) {
            setTimeout(calcGC, 1);
        } else {
            var d2 = new Date();
            var stest = (d2.getTime() - d1.getTime()) / 1000.0 + "s";
            $("#lblTest").html(stest + "<BR/>" + $("#lblTest").html());
        }
    }
    calcGC();
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Many thanks for this excellent explanation and example jfriend00 we'll try this on our page ASAP. Wonderful! – ajayel Oct 17 '11 at 09:11
4

My feeling is that there is a bug in IOS5 safari, because once I've started to get these errors, I get them all over the place (including the Google mobile search page), with no visible timeout/pause occurring. Killing Safari and restarting it fixes the problem (until it happens again - perhaps it's a genuine timeout which puts Safari into the broken state initially).

Have you tried killing Safari via the multitasking menu and restarting it?

Will Dean
  • 39,055
  • 11
  • 90
  • 118
1

Sounds like Apple reduced the execution timeout for javascript in iOS5. This was probably due to general speed improvements in Mobile Safari and also the inclusion of the Nitro engine for UIWebViews.

sciritai
  • 3,688
  • 1
  • 17
  • 22
0

I too think it's an IOS5 browser bug. We've got a similar issue. We have a large RIA application with lots of Javascript code, and after a page refresh the browser starts throwing timeout exceptions. iOS4 didn't have this issue. And once the exception starts happening, it goes from bad to worse, until the browser gets totally broken: other unrelated pages throw the exception and refuse to render.

Killing Safari and restarting it makes the problem go away.

VladH
  • 702
  • 1
  • 6
  • 16
  • hi VladH and Will Dean, our error could be consistently reproduced even with fresh browser and has gone away completely with jfriend00s approach. Although interestingly if two browser (or full screen web) windows are running the same code at the same time we get the javascript error described in the original question. – ajayel Oct 18 '11 at 23:19
  • @VladH, are you using window.setTimeout or window.setInterval in your app? – illvm Oct 24 '11 at 19:11
  • No, we are still trying to pinpoint where exactly in our application is the piece that causes the timeout - our app is vast and highly asynchronous. However, even when we manage to find a work-around, there is larger issue: once these timeout errors start to popup, Safari is broken - one refreshes the page a few times and now JS scripts that had no problem, stop working even on unrelated sites in separate tabs. Our scripts should not be able to break the browser. BTW, we just reproduced this on google maps site, by toggling satellite layer on and off. – VladH Oct 25 '11 at 19:17