How can I make a cancel button halt this computation? - javascript

I have three functions, handleSubmit handleCancel, and solve. HandleSubmit triggers a compute-heavy process, solve. handleCancel should stop that process early.
My simplified code looks like this:
import { solve } from './solve.js';
let AllowedToRun = [false];
function handleSubmit() {
allowedToRun[0] = true;
solve(allowedToRun);
allowedToRun[0] = false;
}
function handleCancel() {
allowedToRun[0] = false;
}
// solve.js
function solve(allowedToRun) {
let n = 0;
while (n < 100000000 && allowedToRun[0]) {
n++;
console.log(n);
await sleep(0); // unblock UI by chunking compute
}
}
Essentially, once the user clicks submit, a compute heavy operation starts running. I chunk the compute with await sleep(0), a promisified setTimeout, allowing for the UI to be responsive. If at any point the user clicks cancel, I mutate allowedToRun, which will result in the while loop in solve failing, canceling the computation early.
This is fine, and it works, but I would like to have this functionality without the use of mutating arrays and/or global variables. Is there a way this can be done without that? I would also like to import solve, so I cannot use any module-level variables in my solution.

I would like to have this functionality without the use of mutating arrays and/or global variables
The standard solution is to use an AbortSignal, not an array. But either way, it's an object carrying state, and you'll have to mutate it.
function solve(abortSignal) {
let n = 0;
while (n < 100000000 && !abortSignal.aborted) {
n++;
console.log(n);
await sleep(0); // unblock UI by chunking compute
}
}
or preferably
function solve(abortSignal) {
abortSignal.throwIfAborted();
let n = 0;
while (n < 100000000) {
n++;
console.log(n);
await sleep(0, abortSignal); // unblock UI by chunking compute
}
}

Related

JavaScript: Stop a For loop with setTimeout function and this keyword

Basically this pulls a list of visible links (some have display:none because of a filter that I've done prior) and clicks individually on each one with a time delay, as each link takes about 2 seconds to process. However, sometimes the link does not process and gives an error message. When this condition applies, I want to stop this process as it will only fill the server with requests where everyone returns an error. As I usually do, I already tried to create an if (condition) { return; }, but I was not very successful with this particular function.
var x = 1,
myVar = "",
ModelA = true,
VarOne = $('#axws a.icon_x').filter(":visible");
for (i = 0; i < 100; i++) {
if (ModelA) {
$(VarOne).eq(i).each(function() {
var timeNow = time * x;
setTimeout(function(myVar) {
$(myVar).click();
}, timeNow, this);
++x;
});
}
}
Also, I just want to say that my javascript is not excellent, I learned it myself to automate some things that make my life easier (and I'm still learning). If anyone can help me and take the time to explain to me why it works that way, I'd be very grateful. I've been trying to find a solution for 4 hours and I couldn't get it to work. Thanks again.
Edit:
In response to the first comments, I already tried to use a break, but the break gives me an "Unsyntactic break" error. From what I understand from searching this forum, a break cannot be used with .each (I could be wrong).
I also tried with return, both false and true (just in case), but none of them worked, the loop catches the console.log I put before the return, but it doesn't stop the function.
I've also tried with an if else, so that it only clicks on the next one if it doesn't catch the condition, but the loop goes for the if (writing the console.log) and the else (clicking the link).
Unfortunately, it wasn't for lack of trying. I'm not really getting it.
Edit 2: (Solved)
I managed to solve it and I'll leave the answer here so that people who go through the same problem can solve it. Unfortunately I didn't find a solution to the problem here on the forum.
I think the problem with this was that I was using the this keyword which made the loop just being done in the setTimeout function.
setTimeout(function(myVar) {
$(myVar).click();
}, timeNow, this);
Basically I redid the loop so I don't have to use the this keyword:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
} //New function to set a time out
var ModelA = true;
var VarOne= $('#axws a.farm_icon_x').filter(":visible");
var nVarOne = $('#axws a.farm_icon_x').filter(":visible").length; //new var to set the exact i length to the exact number of links
async function load () { // wrap loop into an async function for sleep to work
for (let i = 0; i < nfarm; i++) {
if (ModelA) {
$(VarOne).eq(i).each(function() {
VarOne.eq(i).click();
console.log("Model A: " + i);
});
}
var time = (Math.random() * (5000 - 2000) + 2000).toFixed(0); //var to set sleep time
await sleep(time); // then the created Promise can be awaited
}
}
load(); //loop function
}
That said, all that remains is to put the condition for the loop to stop if { condition == true}. As the loop will repeat the for and continue where the previous promise was, I just had to do, for example, this:
(...) if (ModelA && !condition) { ... }

Temporarily unfreeze UI to allow a DOM update

I have some code that functions like so. It will iterate over an array with forEach, perform a heavy task that freezes the UI with the current iteration, push the returned value from that heavy task to an array, and then return the array being pushed to after the forEach has concluded. This is how it would work:
// generate a dummy array
var array = Array.from(Array(1000000).keys()):
function doSomething() {
// the array to push to
var pushArray = [];
array.forEach((item) => {
// perform a heavy task...
const output = doHeavyThing(item);
// ...and push the heavy task result to an array
pushArray.push(output);
// (this DOM call will not work, because doHeavyThing blocks the UI)
document.getElementById('updateDiv').innerHTML = item;
})
// return the array that was pushed to
return pushArray;
}
const result = doSomething();
return result;
Is there any way I can temporarily pause the execution of the forEach loop, unfreezing the UI, to allow for a DOM update? This would only require a couple of milliseconds.
I cannot use web workers in this situation, because the data being returned is so large that any calls to postMessage would crash the browser on mobile.
I also haven't been able to get setTimeout to work, because it is asynchronous - meaning I cannot return anything. I also cannot reliably use callbacks.
Does anyone have an idea on how to solve this problem? Clarifying questions are always welcome.
My doHeavyThing function is a function from the seek-bzip package:
bzip.decodeBlock(compressedDataBlock, 32);
I believe something like this would work to ensure the event loop can tick in between calls to doHeavyThing.
// create a promise that will resolve on the next tick of the event loop
function sleep() {
return new Promise(r => setTimeout(r));
}
// standin for doHeavyThing. This is enough to induce
// noticable delay on my machine, tweak the # of iterations
// if it's too fast to notice on yours
function doHeavyThing() {
for (let i = 0; i < 100; i++) { console.log(i); }
}
async function handleHeavyLifting() {
const array = Array.from(Array(1000).keys());
const result = [];
for (const item of array) {
document.getElementById('updateDiv').innerHTML = item;
result.push(doHeavyThing(item));
// let DOM updates propagate, other JS callbacks run, etc
await sleep();
}
return result;
}
handleHeavyLifting();
<div id="updateDiv">
None
</div>

Lock in Javascript function

I am making async calls to the server in a loop. Obviously ,it creates a race condition. Here is the code.
for (let i = 0; i < arr.length; i++) {
this.service.execute(arr[i]).pipe(takeUntil(this.ngUnsubscribe)).subscribe(response => {
this.updateData(response); // Avoid race-condition here
}, error => {
//
});
}
I need a mechanism to avoid race-condition. But at the same time I do not want to chain async calls (I want them to work in parallel, only updateData function call should be sync.). Here is a sample how my code would look like if I could use Python-like lock mechanism:
for (let i = 0; i < arr.length; i++) {
this.service.execute(arr[i]).pipe(takeUntil(this.ngUnsubscribe)).subscribe(response => {
// Assume lock is defined
this.lock.acquire();
this.updateData(response); // Avoid race-condition here
this.lock.release();
}, error => {
//
});
}
As clearly seen from above code, only the calls of updateData are waiting for each other, not async calls made to the server. Is it possible to achieve something like that in Javascript, if so how can I do this?
Javascript execution is always single-threaded, so unless your updateData function contains async calls itself, there is no race condition.

Waiting until for loop iteration to be completed visually before going onto next one

function start_map_one() {
for (var i = 0; i < 15; ++i) {
$.when(update_map()).then(evolve('W','W1',18,2,sea_limitations,20,350));
}
}
here, update_map() updates a div. However instead of the div updating visually 15 times in sequence, it appears to wait until all 15 iterations are complete and then display the finished div.
So im looking for this order ideally:
update_map() map information is used to update the div - user
sees the visual result... then...
evolve() map information
updated behind the scenes
update_map() map information is used to update the div - user
sees the visual result... then...
evolve() map information
updated behind the scenes
etc etc 15 times
Consider using recursion for this. Without knowing too much about what the code does, it could look something like this...
function start_map_one() {
start_map_one_helper(15);
}
function start_map_one_helper(count) {
if (count <= 0) {
return;
}
$.when(update_map()).then(function () {
evolve('W','W1',18,2,sea_limitations,20,350);
start_map_one_helper(count - 1);
});
}
Note that the then() callback needs to be wrapped in a function, otherwise it executes right away.
You may need to wrap the recursive call in a setTimeout to see the changes on the screen...
function start_map_one() {
start_map_one_helper(15);
}
function start_map_one_helper(count) {
if (count <= 0) {
return;
}
$.when(update_map()).then(function () {
evolve('W','W1',18,2,sea_limitations,20,350);
setTimeout(function() {
start_map_one_helper(count - 1);
});
});
}
It's because you don't give the browser enough time to change the display. Just add a delay in between the execution of each iteration. Try this solution
function start_map_one() {
for (var i = 0; i < 15; ++i) {
setTimeout(function() {
$.when(update_map()).then(evolve('W','W1',18,2,sea_limitations,20,350));
}, i * 100);
}
}
Change the 100 to a time suitable for you.
Also, you're not encapsulating the evolve function (Which means you're calling evolve during the loop instead of calling it through then. Simply wrap it in a function.

Animating a recursive backtracking algorithm in javascript

I'm trying to create a live demo of a backtracking algorithm (with simple forward checking) in javascript. I've gotten the algorithm down pat in its recursive form, but now I'm stuck trying to animate it using javascript's setTimeout or setInterval, which I'm assuming would require me to convert the recursive solution to an iterative one. Here's the function (rewritten to be a little more general):
function solve(model) {
if (model.isSolved()) return true;
var chosen = chooseVariable(model); //could be random or least constrained
var domain = model.getDomain(chosen);
var i, assn;
for (i = 0; i < domain.length; i++) {
assn = domain[i];
model.set(chosen, assn);
if (solve(model)) return true;
else model.undo();
}
return false;
}
As you can see, I've made it so that the model can undo it's own actions, rather than having a separate action stack or cloning the model at each level of recursion. Is there a way to convert the function above into one that could be used with setTimeout or setInterval? Would I have to significantly change the model/add another stack to keep track of the chosen variable/attempted assignments? Do I need a closure with mutating variables? I'm mostly looking for pseudocode to point me in the right direction.
I'm assuming this require me to convert the recursive solution to an iterative one.
No, right the other way round. Yours still is iterative in some parts (the for-loop).
You will have to make the steps asynchronous, so that each step takes a callback which is fired when its animation is done and you can continue. Since you will want to animate every single iteration step, you will have to make them asynchronous with a recursive-like callback - continuation passing style.
Here's how:
function solve(model, callback) {
if (model.isSolved())
return callback(true);
var chosen = chooseVariable(model); // could be random or least constrained
var domain = model.getDomain(chosen);
var i = 0, assn;
(function nextStep() {
if (i < domain.length) {
assn = domain[i];
model.set(chosen, assn);
solve(model, function(solved) {
if (solved)
callback(true);
else {
model.undo();
i++;
nextStep();
}
});
} else
callback(false);
})();
}
Now you can simply make this recursive variant asynchronous by introducing setTimeout where you need it (usually after displaying the model state):
function solve(model, callback) {
if (model.isSolved())
return callback(true);
var chosen = chooseVariable(model); // could be random or least constrained
var domain = model.getDomain(chosen);
var i = 0, assn;
(function nextStep() {
if (i < domain.length) {
assn = domain[i];
model.set(chosen, assn);
solve(model, function(solved) {
if (solved)
callback(true);
else {
model.undo();
i++;
setTimeout(nextStep, 100);
}
});
} else
setTimeout(callback, 100, false);
})();
}
You could program it asynchronously using for example deferreds. jQuery provides an implementation of deferreds and you could have a look at this example which uses timeouts:
http://api.jquery.com/deferred.promise/#example-0
Of course you need only one timeout which always resolves (succeeds).

Categories

Resources