I have a function running on document load that copies the contents of a select object to other select boxes (to conserve network bandwidth).
The function is taking a few seconds to complete, so I wanted to mask the main div (to give the user the idea that something is happening).
Unfortunately, the mask is not showing up until after the function completes:
// I want the mask to show immediately here, but never gets shown
$('#unassignedPunchResults').mask('Getting results');
$('.employeeList').each(function (i) {
// this is freezing the browser for a few seconds, the masking is not showing
$('#employeeList option').clone().appendTo(this);
});
$('#unassignedPunchResults').unmask();
How can I interrupt the javascript after the mask() call to flush that event and continue, so the user can see the mask while the longer processing (the each()) processes?
Put the rest of the code in a setTimeout(function() { ... }, 0) call.
I've been thinking a while about this.
The first solution is to use the settimeout function.
However it could be a little 'dirty' because you add an arbitrary delay. A more proper logic would be to execute the $('.employeeList').each(function (i)... function after the mask function has benne executed and rendered.
Jquery allows us to do that with the deferred functions like then which xecutes after a deferred condition has been satisfied.
So try with:
// I want the mask to show immediately here, but never gets shown
$('#unassignedPunchResults').mask('Getting results').then(function(){
$('.employeeList').each(function (i) {
// this is freezing the browser for a few seconds, the masking is not showing
$('#employeeList option').clone().appendTo(this);
});
});
In general, using settimeout with an arbitrary number of ms is a solution which works for simple cases, but if you have multiple settimouts in a complex code then you could have synchronizaton problems.
or use a worker, but then you need to discard msie < 10
or break up your calculations in segments that run for less than 500 ms and use setinterval to sread the loading over 5 seconds.
google simulate threading in javascript for code examples.
Related
I have a simple JavaScript function that manipulates the DOM (heights of elements for layout reasons).
The function get's called on window's resize (throttled to 1s) and on button click.
In my function everything is wrapped inside a _.delay() function in order for the script to wait 1s for a triggered animation to finish.
The problem is that sometimes the function get's called fast on after another and the second call starts before the first call ending. Now the function calls are doing their things simultaneously and everything get's bad.
My question:
How can I tell the function to only run one at a time? Some kind of lock would be good that locks the second call from executing. It would be great if this second call still executes, but only after the first call remove the lock.
Is something like this possible in JavaScript and how?
EDIT
Here is a code example of how the script looks like:
function doStuff() {
var stuff = $('[data-do-stuff]');
var height = stuff.height();
// Add CSS class that changes height of stuff
// Class starts an animation of duration of 1s
stuff.addClass('active-stuff');
// Wait 1s for the animation started by added class
_.delay(function() {
stuff.height(height * 42);
}, 1000);
}
$(window).on('resize', _.throttle(function() {
doStuff();
}, 1000));
$('.tasty-button').on('click', function() {
doStuff();
});
This is not a working example, just a gist of what the general structure of my script is.
If I e.g. click multiple times on the tasty button (about 3x in 1s) it messes with everything. (In my real script, I have got more trigger so just disabling the button for 1 second doesn't do the trick -.-)
I would like it to behave like this: If doStuff executes, lock every call until doStuff finishes with executing and then execute the locked calls afterwards.
PROMISES in Javascript is what you are looking for.
Without code examples, it's hard to suggest solutions specific to your question. However, here's some thoughts on your overall problem:
What you're experiencing is a called a "race condition" where a part of your application depends on multiple functions finishing at undetermined times.
Generally, there are two ways to handle situations like this:
1) Use callbacks. About Callbacks
2) As another user suggested, use JS promises. About JS Promises
This is a very simple use case. Show an element (a loader), run some heavy calculations that eat up the thread and hide the loader when done. I am unable to get the loader to actually show up prior to starting the long running process. It ends up showing and hiding after the long running process. Is adding css classes an async process?
See my jsbin here:
http://jsbin.com/voreximapewo/12/edit?html,css,js,output
To explain what a few others have pointed out: This is due to how the browser queues the things that it needs to do (i.e. run JS, respond to UI events, update/repaint how the page looks etc.). When a JS function runs, it prevents all those other things from happening until the function returns.
Take for example:
function work() {
var arr = [];
for (var i = 0; i < 10000; i++) {
arr.push(i);
arr.join(',');
}
document.getElementsByTagName('div')[0].innerHTML = "done";
}
document.getElementsByTagName('button')[0].onclick = function() {
document.getElementsByTagName('div')[0].innerHTML = "thinking...";
work();
};
(http://jsfiddle.net/7bpzuLmp/)
Clicking the button here will change the innerHTML of the div, and then call work, which should take a second or two. And although the div's innerHTML has changed, the browser doesn't have chance to update how the actual page looks until the event handler has returned, which means waiting for work to finish. But by that time, the div's innerHTML has changed again, so that when the browser does get chance to repaint the page, it simply displays 'done' without displaying 'thinking...' at all.
We can, however, do this:
document.getElementsByTagName('button')[0].onclick = function() {
document.getElementsByTagName('div')[0].innerHTML = "thinking...";
setTimeout(work, 1);
};
(http://jsfiddle.net/7bpzuLmp/1/)
setTimeout works by putting a call to a given function at the back of the browser's queue after the given time has elapsed. The fact that it's placed at the back of the queue means that it'll be called after the browser has repainted the page (since the previous HTML changing statement would've queued up a repaint before setTimeout added work to the queue), and therefore the browser has had chance to display 'thinking...' before starting the time consuming work.
So, basically, use setTimeout.
let the current frame render and start the process after setTimeout(1).
alternatively you could query a property and force a repaint like this: element.clientWidth.
More as a what is possible answer you can make your calculations on a new thread using HTML5 Web Workers
This will not only make your loading icon appear but also keep it loading.
More info about web workers : http://www.html5rocks.com/en/tutorials/workers/basics/
I want to display a spinner before some complicated function, i.e. dummyCounter(). The code looks like:
function add1() {
msg.html('start counting~<br \>');
document.body.appendChild(div);
spinner.spin(div);
// display spinner before doing stuff
dummyCounter();
}
jsfiddle: http://jsfiddle.net/eGB5t/
However the spinner shows after the dummyCounter() function is finished.
I try to use callback to force spinner display earlier but still no good. Can anybody help? Thanks.
jsfiddle: http://jsfiddle.net/eGB5t/2/
You have a thinking failure. Spinners are usually used for asynchronous tasks, so you can see that there is something in progress. A callback is then used to remove the spin when the async action has finished, since you cannot tell before it starts when it will finish.
I made up a quick example to show you, how such an async function would work in this case, and you can clearly see how the spinner appears slightly before "google finished" appears.
http://jsfiddle.net/eGB5t/4/
I added the following instead of your counting method:
$.ajax("http://google.de").always(function() {
msg.append("google finished");
});
You add the spin before you count, then it counts, then you could remove the spinner. This is perfecty fine. Thing is, if you would count to let's say 9999999999999 (so it would take some seconds), a normal for loop like you're doing is completely blocking the browser, so you won't have any repaints (and therefore no spinner) at all, while the loop is running.
What you would have to do (in this case) is to introduce a worker to have multithreading functionality in javascript.
var x;
function add1() {
msg.html('start counting~<br \>');
spinner.spin(div);
x= setTimeout(document.body.appendChild(div),500);
}
Ready to face the backlash as I'm aware that this is an oft-asked question here, and fully aware that there are "much better" ways of accomplishing what I want to achieve...
I have a long running JavaScript operation in a function which can take up to 10 seconds to run. I have a "loading div" that I can show and hide at will. Normally this show/hide operation works very quickly but if I attempt to do so in the same call chain as my long running function, the browser does not get chance to reflow and show the loading div before it is locked up by the long running function.
I have tried:
1 - just showing the div then calling the function:
$('#loadingDiv').show();
longRunningFunction();
2 - showing the div then calling the long running function inside a setTimeout to be executed after the current function has finished:
$('#loadingDiv').show();
setTimeout(function(){longRunningFunction();}, 0);
3 - same as 2 but with a longer timeout:
$('#loadingDiv').show();
setTimeout(function(){longRunningFunction();}, 100);
With an even longer timeout (say, 5000 milliseconds) I am able to get the loading screen to show and my long running function to execute properly - but obviously this adds to the execution time and I guess won't be cross-browser compatible (it just so happens that the browser I am using happens to do a repaint in those XXXX milliseconds - another browser may not?)
I've also tried a mish mash of tricks I've seen on SO and elsewhere to do with changing the offsetLeft / offsetTop on divs on the page, changing self.status, but everything I do seems to have the same effect: lock up the browser for a few seconds, with the old content on the page, then show the loading div once the long running function has finished.
I know what I'm doing is "wrong" but it is what I want to do for the moment - ultimately I will:
1) Break this code down into smaller chunks
2) Potentially move it into a Web Worker
3) Speed it up so that it does not take so long and doesn't lock up the browser
But for now can anyone suggest an easy way of forcing that "loading div" to be shown to the user before my long running operation runs?
Use a callback:
$('#loadingDiv').show("fast", longRunningFunction);
This will call your function after the div is shown.
I would possibly suggest looking into using jquery's deferred functionality for this purpose. Deferred objects where made for this and you can find a tutorial regarding how to use this at
http://net.tutsplus.com/tutorials/javascript-ajax/wrangle-async-tasks-with-jquery-promises/
I've created a sample fiddle which shows you how it's used.
http://jsfiddle.net/Nemesis02/PH2nE/1/
Here's how the JavaScript is written
(function($) {
function initLongWait() {
var deferred = new $.Deferred();
setTimeout(function() {
deferred.resolve();
}, 2000);
return deferred.promise();
}
var promise = initLongWait();
$('#wait').fadeIn();
initLongWait().done(function() {
$('#wait').fadeOut();
$('#done').fadeIn();
});
})(jQuery);
jsFiddle Demo
You can take the approach you used in #2 and it should work.
$("#loadingDiv").show();
setTimeout(function(){
longRunningFunction();
$("#loadingDiv").hide();
},10);
When looking to improve a page's performance, one technique I haven't heard mentioned before is using setTimeout to prevent javascript from holding up the rendering of a page.
For example, imagine we have a particularly time-consuming piece of jQuery inline with the html:
$('input').click(function () {
// Do stuff
});
If this code is inline, we are holding up the perceived completion of the page while the piece of jquery is busy attaching a click handler to every input on the page.
Would it be wise to spawn a new thread instead:
setTimeout(function() {
$('input').click(function () {
// Do stuff
})
}, 100);
The only downside I can see is that there is now a greater chance the user clicks on an element before the click handler is attached. However, this risk may be acceptable and we have a degree of this risk anyway, even without setTimeout.
Am I right, or am I wrong?
The actual technique is to use setTimeout with a time of 0.
This works because JavaScript is single-threaded. A timeout doesn't cause the browser to spawn another thread, nor does it guarantee that the code will execute in the specified time. However, the code will be executed when both:
The specified time has elapsed.
Execution control is handed back to the browser.
Therefore calling setTimeout with a time of 0 can be considered as temporarily yielding to the browser.
This means if you have long running code, you can simulate multi-threading by regularly yielding with a setTimeout. Your code may look something like this:
var batches = [...]; // Some array
var currentBatch = 0;
// Start long-running code, whenever browser is ready
setTimeout(doBatch, 0);
function doBatch() {
if (currentBatch < batches.length) {
// Do stuff with batches[currentBatch]
currentBatch++;
setTimeout(doBatch, 0);
}
}
Note: While it's useful to know this technique in some scenarios, I highly doubt you will need it in the situation you describe (assigning event handlers on DOM ready). If performance is indeed an issue, I would suggest looking into ways of improving the real performance by tweaking the selector.
For example if you only have one form on the page which contains <input>s, then give the <form> an ID, and use $('#someId input').
setTimeout() can be used to improve the "perceived" load time -- but not the way you've shown it. Using setTimeout() does not cause your code to run in a separate thread. Instead setTimeout() simply yields the thread back to the browser for (approximately) the specified amount of time. When it's time for your function to run, the browser will yield the thread back to the javascript engine. In javascript there is never more than one thread (unless you're using something like "Web Workers").
So, if you want to use setTimeout() to improve performance during a computation-intensive task, you must break that task into smaller chunks, and execute them in-order, chaining them together using setTimeout(). Something like this works well:
function runTasks( tasks, idx ) {
idx = idx || 0;
tasks[idx++]();
if( idx < tasks.length ) {
setTimeout( function(){ runTasks(tasks, idx); },1);
}
}
runTasks([
function() {
/* do first part */
},
function() {
/* do next part */
},
function() {
/* do final part */
}
]);
Note:
The functions are executed in order. There can be as many as you need.
When the first function returns, the next one is called via setTimeout().
The timeout value I've used is 1. This is sufficient to cause a yield, and the browser will take the thread if it needs it, or allow the next task to proceed if there's time. You can experiment with other values if you feel the need, but usually 1 is what you want for these purposes.
You are correct, there is a greater chance of a "missed" click, but with a low timeout value, its pretty unlikely.