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)
Related
This might not have the greatest title. I'm trying to understand call back functions, and I was wondering how replacing prompt() in the following code could be achieved, without losing the for loop?
for(i=0;i<4;i++){
let x = prompt("Input an integer");
// store input into an array
}
I've tried something like:
for(let i = 0; i<4; i++){
let x = document.getElementById("someId");
x.addEventListener("click", rcvInput(function(i){
if(i == 3){
x.removeEventListener("click", rcvInput)
}
}));
}
function rcvInput(callback){
//store input into an array
callback();
}
I know this can be done without the for loop, I'm more curious if callbacks could be able to pause the loop and wait for input?
Depending on what your end goal is, I'm pretty sure there's a better way to do it. But for the sake of doing that:
You can create a method that returns a promise that resolves when a click happens. Then you can use async/await to do what you need.
By using a Promise and awaiting on it, you can technically "pause" your for loop until something happens. In this case, a click.
Remember the method that encloses the for loop has to be async.
function getClick() {
return new Promise(acc => {
function handleClick() {
document.removeEventListener('click', handleClick);
acc();
}
document.addEventListener('click', handleClick);
});
}
async function main() {
for (let i=0;i<4;i++) {
console.log("waiting for a click", i);
await getClick();
console.log("click received", i);
}
console.log("done");
}
main();
Try it in this plunkr.
To acheieve:
for(var i=0;i<4;i++){
let x = prompt("Input an integer"); // WAIT FOR PROMPT
// ...
// LOOP CODE AFTER PROMPT
}
you can use recursion:
function promptLoop(count){
let x = prompt("Input an integer");
// ...
// LOOP CODE AFTER PROMPT
if (count > 0) promptLoop(count - 1)
}
and use it like so:
promptLoop(4);
Your second scenario is different, and can be adapted like so:
function loop(count, method) {
if (count > 0) method(() => loop(count - 1, method), count);
}
Your function would then take a next callback, like so:
function toBeLooped(next){
// do stuff
next() // continues loop
}
loop(3, toBeLooped);
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);
}
This question already has answers here:
Implementing a pause and resume mechanism for javascript loop execution
(2 answers)
Closed 7 years ago.
I have a start button that when clicked runs a function that loops. How do I get a stopBTN.onClick to stop the running loop?
https://jsfiddle.net/vduxbnkj/
startBTN.onClick = function(){ runLoop(); }
function runLoop(){
while(condition true){
getFolderContentsLoop();
}
}
function getFolderContentsLoop(){
//loop through folders & files looking for .txt file and if "finished"
delete files and folders
}
If you're running a simple for (..) loop, this cannot be stopped via external influence. Everything is happening on the same thread in Javascript, unless your code "ends" at some point and returns control to the browser for a while no UI interaction can happen. The easiest way to have a "loop" is via a setTimeout or setInterval:
interval = null;
startBTN.onclick = function () {
var i = 0;
interval = setInterval(function () {
console.log(i++); // this is inside your loop
}, 1);
};
stopBTN.onclick = function () {
clearInterval(interval);
};
Javascript is single threaded and as long it is in a loop, it can't give control to other code to stop it. But if you have a special kind of loop that is implemented with setTimeout:
function loopStep() {
...
}
function loop() {
loopStep();
setTimeout(loop, 0);
}
then you can add a flag to be able to stop loop's execution:
var flag = true;
function loop() {
if (!flag) return;
loopStep();
setTimeout(loop, 0);
}
and then you can define your stop function:
function stop() {
flag = false;
}
I usually work around this by making my own boolean test as the while condition, like so:
var keepLooping = false;
while(!keepLooping){
document.getElementById("loopButton").onclick = function(){
keepLooping = true;
}
}
while(keepLooping){
//do something here
document.getElementById("loopButton").onclick = function(){
keepLooping = false;
}
}
The only method I can think of is to create a boolean at the very beginning, and set stopBTN.onclick as a function that switches the variable. Then put an if condition that uses break if the boolean is switched.
var r = false;
startBTN.onClick = function(){ runLoop(); }
stopBTN.onClick = function(){r = true; }
function runLoop(){
while(condition true){
getFolderContentsLoop();
if(r){
break;
}
}
}
function getFolderContentsLoop(){
/*loop through folders & files looking for .txt file and if "finished"
delete files and folders*/
}
It's crude, but it should work.
I have a loop that runs indefinitely until I tell it to stop. I am actually using requestAnimationFrame and a lot more is going on, but the below example is just to simplify my question.
var _stop = false;
var loop = function () {
while (!_stop) {
if (some condition is met) {
stop();
}
/* Do something. */
loop();
}
};
function stop() {
_stop = true;
}
Now this all works great, but it will still run /* Do something */ one more time before it actually stops. I want it to stop immediately and return.
Of course this can be done like so:
if (some condition is met) {
stop();
return;
}
But is there a way to include the return part into stop();? This doesn't do what I want for obvious reasons:
function stop() {
_stop = true;
return;
}
But is there a way to achieve this?
var _stop = false;
try {
var loop = function () {
if(!_stop) {
if (some condition is met) {
stop();
}
/* Do something. */
loop();
}
};
} catch(e) {
}
function stop() {
_stop = true;
throw new Error("USE IT WITH PRECAUTION");
}
The loop above does you job of exiting entire loop, But I will say its horribly wrong way of doing thing as ideally function should be
1) mutating the state variables
2) or should be computing the values.
3) or should be determining error state to stop the execution further
It should never be bothered about how the control flow of function caller is and ways to stop function caller execution flow.
It sounds like you want to check the condition (before) every time the work is done. To do this with a _stop variable (as opposed to simply checking the condition in the while condition itself), you have to:
Set the variable based on the condition before starting the loop
Do your work
Set the variable based on the condition before the next loop iteration
Whether you accomplish this with a while() loop or a do while() loop, the process will be the same. Adding a pre-loop check to your example will prevent the work from being done if the user has already exited:
var _stop = false;
// Call loop() when required
var loop = function () {
// Check condition before first iteration
if (/* some condition is met */) {
stop();
}
while (!_stop) {
/* Do your work */
// Check condition before every subsequent iteration
if (/* some condition is met */) {
stop();
}
loop();
}
};
function stop() {
_stop = true;
}
Is there a reason you are recursively calling loop() instead of calling it once and doing all of your work within the contained loop until the user exits? This more simplified version might work for you:
var _stop = false; // Set it initially, could also use checkStopRequired() here
// Call loop() when required
var loop = function () {
// Check condition before first iteration
_stop = checkStopRequired();
while (!_stop) {
// Do your work, setting _stop to true if work returns early
_stop = !doMyWork();
// Check condition before every subsequent iteration
_stop = checkStopRequired();
}
};
function checkStopRequired() {
// Return true if should stop, false if should continue
}
When doing the work required for each loop iteration, you may want to check the exit condition before any expensive operations to allow the whole thing to halt as soon as an exit condition is met, as opposed to waiting for the work to finish. This obviously depends on what work you're doing and what the exit conditions are.
An example of the function to be called within the loop, which will help you set _stop if the stop condition is met part-way through:
function doMyWork() {
// Get user input here...
if (checkStopRequired()) { return false; }
// Get data here...
if (checkStopRequired()) { return false; }
// Do logic here...
if (checkStopRequired()) { return false; }
// Render objects here...
// Return successful result
return true;
}
it might not be the optimal way to do this, but this can be done like this:
var _stop = false;
var flag=0;
var loop = function () {
if(!_stop) {
if(flag){
return;
}
if (some condition is met) {
stop();
_stop = true;
loop();
}
/* Do something. */
loop();
}
};
function stop() {
//_stop = true;
toReturn();
}
function toReturn(){
flag=1;
}
I have the following code
startProgressTimer: function () {
var me = this,
updateProgressBars = function (eventItems) {
alert("updateProgressBars: looping");
alert("me.eventProgressTimerId:" + me.eventProgressTimerId);
var i = 0;
if (eventItems.length === 0) {
alert("internal Stop Begin")
clearInterval(me.eventProgressTimerId);
alert("internal Stop End")
eventItems = [];
}
for (i = 0; i < eventItems.length; i++) {
if (eventItems[i]._eventId) {
eventItems[i].updateProgressBar();
}
}
};
alert("Start Progress Timer");
this.eventProgressTimerId = setInterval(function () {
updateProgressBars([]);
}, 10000);
}
When the function is called I would expect it to run and bottom out only it keeps on looping.
screen output
ALERT:updateProgressBars: looping
ALERT:me.eventProgressTimerId:10
ALERT:internal Stop Begin
ALERT:internal Stop End
ALERT:updateProgressBars: looping
ALERT:me.eventProgressTimerId:10
ALERT:internal Stop Begin
ALERT:internal Stop End
Any ideas
I suspect the problem might be that the code you don't show calls the startProgressTimer() method more than once for the same instance of whatever object it belongs to, and then within the method you store the interval id in an instance property this.eventProgressTimerId - so multiple calls overwrite the property and you'd only be able to cancel the last one.
If that's the case, a simple fix is to declare your eventProgressTimerId as a local variable within startProgressTimer().