justkash
justkash

Reputation: 709

Why does a javascript function work in firefox but not work on Chrome or IE

I have created a method to start an activity indicator and a different method to stop the given indicator. Then when doing tasks that are time consuming, I call the start method before and the stop method after. An example of the call is shown below:

// Show loading
viewControllerShowAjax();

// Tasks that take time including large calculations and ajax requests   
app.timeConsumingFunction();

// Hide loading
viewControllerHideAjax();

This is working on firefox, but not working on either IE or Chrome. Is there a fix for this?

Edit: Below are the functions that I am using:

function viewControllerInit() {
    // Other initializations
    ...

    // Initializing the activity indicator; the activity indicator is simply a jqueryui modal view with a spinjs spinner in it.
    $(this.ajaxModalSelector).dialog({
        resizable   : false,
    dialogClass : "no-close ajax",
    autoOpen    : false,
    height      : 500,
    width       : 500,
    modal       : true,
    open: function( event, ui ) {
        $(this.ajaxModalSelector).dialog("open");
            var opts = {
              lines: 12,
              length: 0,
              width: 20,
              radius: 40,
              color: '#000',
              speed: 1.3,
              trail: 50,
              shadow: false,
            };
            this.spinner = new Spinner(opts).spin(document.getElementById("ajax-modal"));
        },
        beforeClose: function( event, ui ) {
            this.spinner.stop();
        }
    });
}
// Opens the jquery ui modal view
function viewControllerShowAjax() {
    $(this.ajaxModalSelector).dialog("open");
}
// Closes the jquery ui modal view
function viewControllerHideAjax() {
    $(this.ajaxModalSelector).dialog("close");
}

Edit: For more information regarding the problem; I have found that if the hide function is not called for the activity indicator, then it will appear after the time consuming task is completed. Again this is the behaviour in both IE and Chrome but not firefox.

Upvotes: 1

Views: 3191

Answers (1)

user1693593
user1693593

Reputation:

Probable cause

The reason that this doesn't work is that JavaScript is single threaded.

The time-consuming function app.timeConsumingFunction() will block the UI from updating in Chrome and IE.

The reason for it to work in Firefox is simply because FF has a different implementation that allow UI events to execute in-between so to speak (Canary and I believe Chrome as well has an experimental mode that allow UI updates to be executed on a separate thread).

A browser in general cues up different events such as paint events. Then this queue is traversed and executed event by event.

However, if a queue-entry is for example executing the context for a time consuming function, the queue won't have a "breathing space" to continue with the other events in that queue.

That would be among others executing paint events that would otherwise paint the spinner. As it is blocked the spinner won't appear until the time consuming function finishes.

Possible solution using setTimeout

The way to solve this so it works is to part up the execution using either setTimeout or a Web Worker (see below for Web Worker example).

You could for instance try this to see if it improves current code (which I doubt but worth a try as it is simple) - as there is not enough code in the post to actually test this, consider it theory. Try by replacing this line:

app.timeConsumingFunction();

with

setTimeout(app.timeConsumingFunction, 0); //or use 11 if no difference

If that is not enough you will need to do a similar setup but with the internals of the function (which you don't show) which you divide, if possible, into parts that handles different stages of the operations. A skeleton example could be:

app.timeConsumingFunction = function() {

    var hereIsAvailableToAllParts;

    function part1() {
        // step 1 in time-consuming operation
        setTimout(part2, 0);  //start next part
    }

    function part2() {
        // step 2 in time-consuming operation
        setTimout(part3, 0);
    }

    function part3() {
        // step 3 in time-consuming operation
        ...done or next...
    }
    setTimeout(part1, 0);
}

The 0 argument to setTimeout tells the browser to queue this event at first available chance. This might not always work but you can use an interval of about 11 milliseconds instead which is close to the minimum available queue slice.

By splitting up the operations like this you will allow the browser to execute for example paint events in between and therefor be able to see the spinner.

Possible solution using Web Workers

Without knowing your code I can not say Web Workers are usable in this scenario as there are certain restrictions for those such as no DOM access, no storage access etc. The support for Web Workers is however excellent and I would recommend this approach if possible with your code (ref. restrictions).

But if you do pure calculations consider Web Workers as an option:
https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers

A bare-bone example:

In your main script you can allocate a Web Worker like this:

var ww = new Worker(urlToWebWorkerScriptHere);

Web Workers communicates through messages so on "our" side we listen to the message event:

ww.onmessage = function (e) {

    var msg = e.data;

    /// message returned can be anything you like (see next section)
    if (msg === 'Done!') viewControllerHideAjax();
};

The Web Worker uses a separate script which is either loaded as a separate JS-file or you can use a little trick to inline it with the HTML code (shown in the fiddle below).

The script is executed in an isolated context and is where all the calculations take place. When done it signals our main script:

onmessage = function(e) {

    /// you can send any commands you want and call them what you want
    /// for example, I have called the command start

    if (e.data === 'start') {

        /// the time consuming stuff..

        /// when finished, send 'Done!' to main script
        postMessage('Done!');
    }
};

And to start the calculation you would do this:

// Show loading
viewControllerShowAjax();

// Tasks that take time including large calculations and ajax requests   
ww.postMessage('start');

(the hide is called when the 'Done!' message is returned from worker).

You can of course send forth and back many messages, for example to return partly results and to trigger next part of the calculation (or operation).

I have made a simple demo in the following fiddle - notice that you can click the right button even if the loop in the fiddle is doing busy-looping/"heavy work" (in the example you can also see how to inline the Web Worker script instead of loading it separately):

WEB WORKER DEMO

But again, without seeing your code for the work I cannot tell if Web Workers can be used or not.

In any case this should give some pointers to how to solve this.

Upvotes: 10

Related Questions