Let's imagine the following code:
function DoSomethingHard(parameter1, parameter2, parameter3){
// Do Something Hard Here
}
var i;
for(i = 0; i <= stuff.length; i++) {
// "stuff" is an array
DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
}
$( "#button_to_cancel" ).click(function() {
//something to cancel
});
Suppose the array "stuff" has 100 positions, so the for loop will run
100 times, ie, it will do "Do Something Hard" 100 times.
Let's also consider that "DoSomethingHard" takes about 5 seconds to run
completely.
My question is: How do I manage the cancellation of "DoSomethingHard"? For example, if it has already run 50 times, how can I cancel the subsequent executions through a button? I did not succeed in my attempts and it always ends up running the whole loop ....
Thanks in advance :)
Javascript is single threaded, and a for loop executes until it is finished. I would do something like this to allow time for the cancel.
function DoSomethingHard(param){
//do something
}
var i = 0;
var loopInterval = setInterval(function() {
if (i >= stuff.length) {
clearInterval(loopInterval);
return;
}
DoSomethingHard(stuff[i]);
i++
}, 10);
$( "#button_to_cancel" ).click(function() {
clearInterval(loopInterval);
});
You can make use of setInterval to call the function and when you have a click event you can clear the intervals
var mytimeout;
var i;
for(i = 0; i <= stuff.length; i++) {
// "stuff" is an array
mytimeout = window.setInterval(DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3), 2000);
}
$( "#button_to_cancel" ).click(function() {
//something to cancel
window.clearInterval(mytimeout)
});
Simplest way as I see it:
function DoSomethingHard(parameter1, parameter2, parameter3){
//Do Something Hard Here
}
var i;
var active = true; //as of now, we want to process stuff
for(i=0;i<=stuff.length;i++){
//"stuff" is an array
if(active){
DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
}else {
active = true; //reset active in case we want to run loop again later on
break; // break out of loop
}
}
$( "#button_to_cancel" ).click(function() {
active = false;
});
You can't easily cancel it with a click on a button, unless you use recursion or iterators instead of a loop.
But you can cancel the loop inside itsself with a break; statement when some condition is met. For example you could write:
var result;
for(i=0;i<=stuff.length;i++){
result = DoSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
if (result === 'error' || i === 50) break;
}
This will end the loop if result becomes the 'error' (or anything else your return from inside the function) or when i reaches 50.
Now that i think of it, it's possible with a button click, but it requires more code and is inefficient. give me a minute.
Update: I would not advice this ppttern either, but it's pretty flexible:
var exitCondition,
doSomethingHard = function doSomethingHard(parameter1, parameter2, parameter3){
// Do Something Hard Here
},
i,
length = stuff.length,
result;
for (i = 0; i <= length; i++) {
// "stuff" is an array
result = doSomethingHard(stuff[i].something1, stuff[i].something2, stuff[i].something3);
if (exitCondition( result, i )) break;
}
$( "#button1_to_add" ).click(function() {
exitCondition = function( result, index ) {
return index === 50;
});
});
$( "#button2_to_cancel" ).click(function() {
exitCondition = null;
});
The clue here is to have an exit condition (or multiple) that you check inside the loop and have the button update this condition.
You can not stop a for loop from UI interaction because everything is running in a single thread and your action will execute only after loop executes completely. You can use setInterval as #jason p said.
I solve this way:
function DoSomethingHard(parameter1, parameter2, parameter3){
//Do Something Hard
var timer = window.setInterval(function(){
var page = $$('.page').data('page');
console.log("The page is: "+page);
if(page != 'preview'){
//Cancel...
console.log('Aborted');
clearInterval(timer);
}
},1000);
}
That is, i changed the scope. Instead using button click, i monitered when user leave the page, so cancel it.
You need to note that loop is synchronous where as your function isn't. Loop won't wait for DoSomethingHard() to compleye before the next iteration begins.In just a few milliseconds DoSomethingHard has been called over a hundred times! and your loop gets over so in essence break is of no use here.I think no language construct can help here
So what to do?
You need to decide whether to do something or not in the function itself create a global flag and check it inside the function
function DoSomethingHard(){
if(flag50done){
return;
}else{
//do what this fn was meant for
}
}
You can change value of flag50done with a click of a button and further actions would get stopped due to the return
In case DoSomethingHard is some 3rd party function which you cannot modify you can wrap it in another function say runDecider
function runDecider(a,b,c){
//add flag check here
if(flag50done){
return;
}else{
DoSomethingHard(a, b, c);
}
}
and call this in the loop
var result;
for(i=0;i<=stuff.length;i++){
result = runDecider(stuff[i].something1, stuff[i].something2, stuff[i].something3);
}
Related
I have an application using jquery that iterates through a series of checkboxes.
while it iterates through, I have a dialog showing the status of the printing session. I also have a cancel button to cancel the session.
<input type="button" id="cancelButton" onclick="cancel()">
function cancel() {
stop = true;
}
Through each iteration I check if stop is true and if it is, I break out of the loop. The problem however is that if I press the cancel button during the session, the cancel function will only run after the .each loop (kind of defeating the purpose). Is there a way to make the button more responsive or a better way to approach this?
Run the functions in separate threads?
setTimeout(function() { $('[name=check]:checked').each(function() {
printingFunction();
}); }, 1);
You could chain setTimeouts in order to make it possible to check for a Stop. You'd still have to wait for the current operation to complete, but it would let you break out of the loop.
function next()
{
printingFunction();
if (!stop && someOtherCheck)
setTimeout(function() { next(); }, 1);
}
next();
I don't think you can interrupt the .each loop as JavaScript execution is single threaded.
However I think timers are executed asynchronously, you might try something like below to cancel and clear.
Something like the logic below may get you there.
var timeouts = [];
$.each(someArray, function(index, value){
timeouts.push(setTimeout(function(){
console.log(index);
},5000*index));
});
$('#cancel-button').click(function(){
$.each(timeouts, function (_, id) {
clearTimeout(id);
});
timeouts = [];
}):
Also, you may consider playing with .index() if the above doesn't help.
Try it again - I made a change to the shift statement that will clear the error in IE 11:
var ele = Array.prototype.shift.call($checked);
You need to use setTimeout on the each loop call (can be set to 0) so the cancel function gets a chance to execute - something like this:
function cancel() {
stop = true;
}
function printingFunction() {
var x = 0;
while (x < 10000){
x+=1;
}
}
function printLoop($checked) {
if (stop) {
console.log('Stopped - button pushed')
} else {
if ($checked.length > 0) {
var ele = Array.prototype.shift.call($checked);
printingFunction()
}
if ($checked.length > 0) {
setTimeout(function(){printLoop($checked)}, 0);
} else {
console.log('Stopped - array depleted')
}
}
}
var stop = false,
$checked = $('[name=check]:checked');
printLoop($checked)
I am trying to timeout a function in case it has an infinite loop. But the below does not work. Any idea why and how to fix it?
setTimeout(clearValues,1000);
function clearValues(){
i=0;
alert("hi "+i);
}
i=19
function infin(){
while(i>0){
console.log(i);
i++;
}
alert("Done");
}
infin();
In the below case, I get the alert displayed ( a bit later than expected ) and the console statements continue printing even after the alert. That means setTimeout did not wait for the loop to end in this case. Any explanation for this?
setTimeout(clearValues,500);
function clearValues(){
alert("clear");
}
function infin(){
for(i=0;i<10000;){
i=i+0.3;
console.log(i);
}
}
infin();
setTimeout works asynchronously, means it will run after 1000ms and the previous event loop is completed. Since the while loop will never be completed, the callback will never be called.
Add a condition to the loop if you want to exit it.
Another solution might be to use interval:
var code = function(){
console.log('tick');
};
var clock = setInterval(code, 200);
When you don't need it anymore:
clearInterval(clock);
It works, when you made the infin call with a slightly different change.
var i = 0;
setTimeout(clearValues, 1000);
var interval = setInterval(infin, 0);
function clearValues() {
out("clear");
clearInterval(interval);
}
function infin() {
if (i < 10000) { // if you be shure that you
i++;
out(i);
} else { // never reach the limit,
clearInterval(interval); // you can remove the four
} // commented lines
}
function out(s, pre) {
var descriptionNode = document.createElement('div');
if (pre) {
var preNode = document.createElement('pre');
preNode.innerHTML = s + '<br>';
descriptionNode.appendChild(preNode);
} else {
descriptionNode.innerHTML = s + '<br>';
}
document.getElementById('out').appendChild(descriptionNode);
}
<div id="out"></div>
I have the following jquery code with setTimeout function to set a variable to false but it never works and the variable always remains to be true
for( var ff = 0; ; ff++)
{
if( dif == 0){
break;
}
if (locked){
// locked = false;
setTimeout( function(){ locked = false; },2000);
}
else{
LeftKeyPressed();
locked = true ;
setTimeout( function(){ locked = false; },3000);
dif--;
}
}
can anyone to help in how to set the locked variable to false after exactly two seconds from setting it to true.
Here's a fiddle of this issue.
Okey, since the requirements are what they are I dont know what exactly you want but this is closest I could manage to make: http://jsfiddle.net/db6gJ/
It calls method three times and gives you a change to do what you want to do when locked is false or true. Plus, it won't block the event loop (with that infinite for loop) so your site can make other tasks while timeOut call's are running.
javascript code also here:
var locked = true,
timesMax = 3,
timeCurrent = 0;
var inputLoop = function() {
var delay = 2000;
if(locked) {
// Do something when locked is true
console.log("locked is true!");
locked = false;
} else {
// Do something when locked is false
console.log("locked is false!");
locked = true;
delay = 3000;
}
timeCurrent++;
// call again after delay, but call only 3 times
if(timeCurrent < timesMax) {
setTimeout(inputLoop, delay);
}
};
// Launch it
inputLoop();
If you view your javascript console (in chrome: right mouse click -> inspect element -> console) it should print:
locked is true!
locked is false!
locked is true!
Also to be noted: your original code had alert('out'); when it's done, but to be honest, code will continue execution elsewhere while your "inputLoop" code is not doing anything else than waiting callback to be run and it's the way it should be.
If you wan't to know when it is called last time you could modify one of the previous lines to be:
// call again after delay, but call only 3 times
if(timeCurrent < timesMax) {
setTimeout(inputLoop, delay);
} else {
// Last call in this method
console.log('done!');
}
I have a search box that hides all lines in a list that don't contain the entered text.
This worked great until the list became 10,000 lines long. One keystroke is fine but if the user types a several letter word, the function is iterated for each keypress.
What I want to do is to abandon any previous execution of the function if a new key is pressed.
The function is very simple, as follows:
$("#search").keyup(function(e) {
goSearch();
});
function goSearch()
{
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(function(index, element) {
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1)
$(row).show();
else
$(row).hide();
});
}
Thanks
You can't directly. Javascript is not multi-threaded so your function will run and block any key-presses until it is done.
The way this is made tolerable from a user-experience point of view is to not trigger a function immediately on a key event, but to wait a short period of time and then fire the event.
While the user is typing, the timeout function will continually be set and reset and so the gosearch function won't be called, and so the user won't have their typing interrupted.
When the user pauses typing, the timeout will countdown to zero and call the search function, which will run and block typing until it completes. But that's okay (so long as it completes within a second or so) as the user is probably not currently trying to type.
You can also do what you actually asked by breaking up your gosearch function into chunks, where each call to the function: * Reads a counter of the number of lines processed so far, and then processes another 500 lines and increments the counter. * Calls another gosearch using setTimeout with a value of zero for the time. This yields events to other 'threads', and allows for fast changing of search terms.
var goSearchTimeout = null;
var linesSearched = 0;
function keySearch(e){
if(goSearchTimeout != null){
clearTimeout(goSearchTimeout);
linesSearched = 0;
}
goSearchTimeout = setTimeout(goSearch, 500);
}
$("#search").keyup(keySearch);
function highLight(index, element) {
if(index >= linesSearched){
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1){
$(row).show();
else{
$(row).hide();
}
if(index > linesSearched + 500){
linesSearched = index;
goSearchTimeout = setTimeout(goSearch);
return;
}
}
function goSearch(){
goSearchTimeout = null;
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(highLight);
}
If you're going to use timeout callbacks like this, I'd strongly recommend wrapping your code up into jQuery widgets, so that you can use variables on the object to store the variables goSearchTimeout etc rather than having them float around as global variables.
Introduce a counter var keypressCount that is being increased by your keypress event handler. at the start of goSearch() write its value into a buffer. Then at each run of your $(".lplist").each() you ask if the current keypressCount is the same as the buffered one; if not, you return. I would suggest you use a for() though since it is easier to break; than $.each().
Update:
You will have to make sure that there is time for new keypress events to be fired/received, so wrap the anonymous function of your $.each() inside a timeout.
Reference: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/
You can use a global variable to save search string and stop execution when search string changes.
IMPORTANT: You must set a timeout in each iteration so that function execution is paused and global variables are updated, as JavaScript is single-threaded.
Your code would look like this:
var searchString;
$("#search").keyup(function(e) {
// Update search string
searchString = $("#search").val().toLowerCase();
// Get items to be searched
var items = $(".lplist");
// Start searching!
goSearch(items, searchString, 0);
});
function goSearch(items, filter, iterator)
{
// Exit if search changed
if (searchString != filter) {
return;
}
// Exit if there are no items left
if (iterator >= items.length) {
return;
}
// Main logic goes here
var element = items[iterator];
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(filter, 0) != -1)
$(row).show();
else
$(row).hide();
// Schedule next iteration in 5 ms (tune time for better performance)
setTimeout(function() {
goSearch(items, filter, iterator + 1);
}, 5);
}
//I have the following function:
function handle_message(msg)
{
//do work
console.log('some work: '+msg.val);
//call next message
msg.next();
}
//And array of message objects:
var msgs = [ {val : 'first msg'}, { val : 'second msg'}, { val : 'third msg'}];
//I link messages by setting next parameter in a way that it calls handle_message for the next msg in the list. Last one displays alert message.
msgs[2].next = function() {alert('done!')};
msgs[1].next = function() {handle_message(msgs[2]);};
msgs[0].next = function() {handle_message(msgs[1]);};
//Start the message handle "chain". It works!
handle_message(msgs[0]);
//======== Now I do exactly the same thing but I link messages using the for loop:
for (var i=msgs.length-1; i>=0; i--)
{
if (i==msgs.length-1)
{
msgs[i].next = function() {alert('done!');};
}
else
{
msgs[i].next = function() {handle_message(msgs[i+1]);};
}
}
//Start the message handling chain. It fails! It goes into infinite recursion (second message calls itself)
handle_message(msgs[0]);
Can sombody explain why it happens? Or maybe an alternative to this pattern? My case is this: I receive an array with messages and I have to handle them in order, one ofter another SYNCHRONOUSLY. The problem is some of the messages require firing a series of animations (jqwuery animate() which is async) and the following messages cannot be handled until the last animation is finished. Since there is no sleep() in javascript I was trying to use such pattern where the message calls the next one after it is finished (in case of animations I simply pass the 'next' function pointer to animate's "complete" callback). Anyway, I wanted to build this 'chain' dynamically but discovered this strange (?) behaviour.
You need a closure to make it work:
function handle_message( msg ) {
console.log( 'some work: ' + msg.val );
msg.next();
}
var msgs = [{val :'first msg'},{val:'second msg'},{val:'third msg'}];
for ( var i = msgs.length - 1; i >= 0; i-- ) {
(function(i) {
if ( i == msgs.length - 1 ) {
msgs[i].next = function() { alert( 'done!' ); };
} else {
msgs[i].next = function() { handle_message( msgs[i + 1] ); };
}
})(i);
}
handle_message( msgs[0] );
Live demo: http://jsfiddle.net/simevidas/3CDdn/
Explanation:
The problem is with this function expression:
function() { handle_message( msgs[i + 1] ); }
This function has a live reference to the i variable. When this function is called, the for loop has long ended and the value of i is -1. If you want to capture the current value of i (the value during the iteration), you need to an additional wrapper function. This function captures the current value of i permanently (as an argument).
I think the problem is that i doesn't have the value you think it has:
// i is defined here:
for (var i=msgs.length-1; i>=0; i--)
{
if (i==msgs.length-1)
{
msgs[i].next = function() {alert('done!');};
}
else
{
msgs[i].next = function() {
// when this line gets executed, the outer loop is long finished
// thus i equals -1
handle_message(msgs[i+1]);
};
}
}
See point #5 Closures in loops at http://blog.tuenti.com/dev/top-13-javascript-mistakes/
Think about the values you are capturing in the closure.
msgs[i].next = function() {handle_message(msgs[i+1]);};
This captures the value of i, but it changes the next iteration so you get an infinite loop.
By the end of the loop i is -1 so i+1 is going just going to be the same message over and over again.