Convert for loop to recursive function - javascript

I've got a for loop, and would like to convert it into a recursive function.
I have a prompt that should pop up for every item and be dismissed before moving on to the next item in the list. This behavior doesn't seem to work with a normal for loop. The loop finishes before the prompt is even displayed, and then only one prompt shows up, even if there is more than one item in the list for which the prompt should have been displayed.
(I'm not totally sure if it will work with a recursive function, but from what I've read it seems promising. If I'm wrong about this there's no point in doing the conversion.)
I've been looking at other examples, but I can't seem to wrap my head around how exactly they work.
Here's my original loop:
for(var i = 0;i < items.get_count();i++) {
var item = items.getItemAtIndex(i);
//Is the item we're looking at in need of approval?
if (item.get_item("_ModerationStatus") === 2)
{
//Yes, prompt for approval. (code for prompt goes here.)
//Wait until prompt has received a response before going on next list item.
}
else
{
//No, do nothing.
}
}
My attempt looks a bit sad. I'm not really sure where to go from here:
function recursiveCheckRequests(i)
{
if (i < items.get_count())
{
function checkRequest(items, )
//???
}
}
recursiveCheckRequests(0);

You have to call the function from inside itself.
function recursiveCheckRequests(i) {
var item = items.getItemAtIndex(i);
//Is the item we're looking at in need of approval?
if (item.get_item("_ModerationStatus") === 2) {
//Yes, prompt for approval. (code for prompt goes here.)
//Wait until prompt has received a response before going on next list item.
}
if (i + 1 < items.get_count()) {
recursiveCheckRequests(i + 1);
}
}
recursiveCheckRequests(0);

this is what i would do
function recursiveCheckRequests(pos,arr,length)
{
if (pos === length){
return true;
}
//do something here with the array
return recursiveCheckRequests(pos+1,length)
}
recursiveCheckRequests(0,array,array.length);
this way the function is completely independent of the array to be passed and tyou can specify the limit on the iterations to perform.

Related

I have a list of request numbers(strings) displayed in table format in my app and it has pagination, so have multiple page table of contents

i have a for loop to iterate the pages and used each block to iterate request numbers within the table in that page. When request no in the table matches my request it should select that and exit the loop. I am stuck here as I cannot break the loop .
M_SelectRequestNoThen(request) {
let exit = true;
let i;
cy.wait(500);
this.E_TotalPages()
.invoke("text")
.then((text) => {
let total = text;
cy.log("totalpages", total);
for (i = 0; i < total; i++) {
cy.log("i inside while", i);
this.E_RequestRows().each(($el, $index) => {
cy.wrap($el)
.invoke("text")
.then((text) => {
cy.log("text", text);
if (text.trim().includes(request)) {
this.E_RequestSelect(request).click();
exit = false;
}
});
});
i++;
if (exit) {
this.E_NextButton().click();
} else {
break;
}
}
});
}
as i cannot use break in then block;used boolean exit but even that doesnt get updated value outside then block. so even if my request is found it navigates to next page. so how can i break my for loop after 'if (text.trim().includes(request))' condition is satisfied?
You have some issues of control flow here, primarily that you are trying to break inside an asynchronous Promise. However, I don't think you need that complexity and can leverage the test framework better doing something more simple like this instead (might be a little off because I'm not sure exactly what you're testing here):
this.E_RequestRows().each(($el, $index) => {
if ($el.contains(request)) {
this.E_RequestSelect(request).click();
break; // assuming you want to stop this iteration when you find your element
}
});
My scenario is I have 4 pages of lists. if my string is not found in first page it has to click on next page. In that fashion it has to iterate list and pages both until string is found and then exit. If its single page scenario my each block works. if string is not found in that entire first page i have to check outside each block either to go for next page or break the loop.

Run several small test within one 'it' in E2E test using Protractor

I am working on a E2E test for a single-page web application in Angular2.
There are lots of clickable tags (not redirected to other pages but has some css effect when clicking) on the page, with some logic between them. What I am trying to do is,
randomly click a tag,
check to see the the response from the page is correct or not (need to grab many components from the web to do this),
then unclick it.
I set two const as totalRound and ITER, which I would load the webpage totalRound times, then within each loading page, I would randomly choose and click button ITER times.
My code structure is like:
let totalRound: number = 10;
let ITER: number = 100;
describe('XX Test', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
describe('Simulate User\'s Click & Unclick',() => {
for(let round = 0; round < totalRound; round++){
it('Click Simulation Round ' + round, () =>{
page.navigateTo('');
let allTagFinder = element.all(by.css('someCSS'));
allTagFinder.getText().then(function(tags){
let isMatched: boolean = True;
let innerTurn = 0;
for(let i = 0; i < ITER; i++){
/* Randomly select a button from allTagFinder,
using async func. eg. getText() to get more info
about the page, then check if the logic is correct or not.
If not correct, set isMatchTemp, a local variable to False*/
isMatched = isMatched && isMatchTemp;
innerTurn += 1;
if(innerTurn == ITER - 1){
expect(isMatched).toEqual(true);
}
}
});
});
}
});
});
I want to get a result after every ITER button checks from a loading page. Inside the for loop, the code is nested for async functions like getText(), etc..
In most time, the code performs correctly (looks the button checkings are in sequential). But still sometimes, it seems 2 iterations' information were conflicted. I guess there is some problem with my code structure for the async.
I thought JS is single-thread. (didn't take OS, correct me if wrong) So in the for loop, after all async. function finish initialization, all nested async. function (one for each loop) still has to run one by one, as what I wish? So in the most, the code still perform as what I hope?
I tried to add a lock in the for loop,
like:
while(i > innerTurn){
;
}
I wish this could force the loop to be run sequentially. So for the async. func from index 1 to ITER-1, it has to wait the first async. finish its work and increment the innerTurn by 1. But it just cannot even get the first async. (i=0) back...
Finally I used promise to solve the problem.
Basically, I put every small sync/async function into separate promises then use chaining to make sure the later function will only be called after the previous was resolved.
For the ITER for loop problem, I used a recursion plus promise approach:
var clickTest = function(prefix, numLeft, ITER, tagList, tagGsLen){
if(numLeft == 0){
return Promise.resolve();
}
return singleClickTest(prefix, numLeft, ITER, tagList, tagGsLen).then(function(){
clickTest(prefix, numLeft - 1, ITER, tagList, tagGsLen);
}).catch((hasError) => { expect(hasError).toEqual(false); });
}
So, each single clicking test will return a resolve signal when finished. Only then, the next round will be run, and the numLeft will decrease by 1. The whole test will end when numLeft gets to 0.
Also, I tried to use Python to rewrite the whole program. It seems the code can run in sequential easily. I didn't met the problems in Protractor and everything works for my first try. The application I need to test has a relatively simple logic so native Selenium seemed to be a better choice for me since it does not require to run with Frond-end code(just visit the webapp url and grab data and do process) and I am more confident with Python.

Calling a function inside a loop breaks it

I have a function that loops an object from a MongoDB collection. It's all possible connections for some mail transportation posts. Once I get one connection, I want to immediately remove the inverse connection from the connections object, for example, postA=1 and postB=2, I want to remove postA=2 and postB=1 (removeConnection function does that).
I can't understand why it only returns one 'A' on the console when I try to run that function inside calculateRoute, and returns three 'A' (which is what it should) when I remove it. That function is somehow breaking the loop.
calculatedRoutes = calculateRoute(store.postid, client.postid, connections, []);
function calculateRoute(actualPost, finalPost, connections, routes) {
for(i=0; i < connections.length; i++) {
if(actualPost == connections[i].postA) {
console.log('A');
// If I remove this, the console shows A three times. If I keep this, only shows 1 time.
connections = removeConnection(connections[i].postB, connections[i].postA, connections);
}
}
return routes;
}
function removeConnection(postA, postB, connections) {
for(i=0; i < connections.length; i++) {
if(connections[i].postA == postA && connections[i].postB == postB) {
delete connections[i];
//break;
}
}
return connections;
}
It appears that you are modifying the collection that you are iterating over when you callremoveConnection. I would venture to say that after the first loop, connections.length is less than your loop control variable, which would cause the loop to terminate. What are the contents of connections after the function call?
In general, directly modifying a collection you're iterating over is bad practice. A better option would be to project the collection into a new one that contains the values you want (using map,filter,etc). That way your not mutating anything.
Fixed it by adding var to i=0 on each for. Can someone explain?
for(var i=0; i < connections.length; i++) {...

Why does function only run once?Trying to run function multiple times,after previous invokation complete, using an counter function

I am currently working on a book with page turn effect in jQuery (no plugin). The page turn effect works fine so far, as long as you click through the pages one by one. But now I want to include a dropdown selection (i.e. a select element) so the user can directly jump to the selected content. I tried to make this work with loops and with the .each() method, so that the turnRightPage/ turnLeftPage function is called repeatedly, until the page with the selected content is shown. But after quite a bit of trial and error and a lot of research, I think loops iterate too fast for my turnRightPage /turnLeftPage()-function (which are the transform functions that turn the respective page), in that the loop is done, before the function has completed. I think, what I need to do, is find a way to pause the loop until the function has finished executing and then resume with the next iteration. I think the most promising approach would be using a function with an iteration counter, like it was suggested here:
Javascript: wait for function in loop to finish executing before next iteration (Thanks to jfriend00 at this point) I have also read
Invoking a jQuery function after .each() has completed and
wait for each jQuery
among others, where similar solutions were suggested.
Below is how I tried to implement jfriend00's callback. I added a return statement to break out of that "callback loop", once the number of page turns is completed.
//determine whether to flip pages forward or back - first forward
if(currentPagePos < foundPagePos){ // => turn right page
//determine how many times need to turn page
if (pageDifference > 1 && pageDifference % 2 !=0) {
var numPageTurns = (pageDifference-1)/2;
pageForward (numPageTurns);
} //else if ... rest omitted for brevity
}
function pageForward (numPageTurns){
var i = 0;
function next(){
i++;
if (i <= numPageTurns){
turnRightPage ();
} else {
return;
}
}
next();
};
The full code can be seen here: http://jsfiddle.net/snshjyxr/1/
It DOES turn the page, but only once! What am I missing?
I am still very new to javascript / jQuery so my apologies, if the problem seems all too obvious. Any pointers appreciated. Thx!
The thing is all the page turns are fired, but all at once. You have to wait until each transition is finished to start the next one.
Use a callback function in your turnRightPage and turnLeftPage functions. Example for turnRightPage :
function turnRightPage(callback) {
[...]
//change class AFTER transition (frm. treehouse-site)
$page.on('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function () {
//need to double-set z-index or else secondtime turning page open setting z-index does not work (tried in Chrome 38.0.2125.111 m)
$page.css("z-index", turnedZindex + 1);
$(".turned").removeClass("turned");
$page.addClass("turned");
if(typeof callback == "function") {
callback();
}
});
};
And in your pageForward function, use turnRightPage recursively:
function pageForward(numPageTurns) {
console.log("number of FORWARD page turns: " + numPageTurns);
if(numPageTurns > 0) {
turnRightPage(function(){
pageForward(numPageTurns - 1);
});
}
};
Here is your updated jsfiddle. As you can see, there's a remaining bug when you make several page changes which is caused by the fact that you're adding listeners on the transition end every time a page is turned, and never removing them. So they're all executing every time.
EDIT: jsfiddle updated again without the annoying last bug. As you can see, all it took was to unbind the event listener as soon as it's fired.

Animating an algorithm in Javascript

I am using Raphael.js to visualize a convex hull algorithm.
However I want to be able to step through the different parts of the code (or use something like sleep()/delay()). However, I can't see a way of accomplishing this using setTimeOut(). Any ideas?
For example:
sort(points);
//sleep(...)/delay(...)/pause until click?
for(...) {
message('Foo thing');
//sleep(...)/delay(...)/pause until click?
while() {
message('Comparing points');
//sleep(...)/delay(...)/pause until click?
}
}
In JavaScript there is no way to suspend code execution with sleep function. Executing JavaScript code is designed to be non-blocking.
Solution with using debugger keyword works on Chrome as well. You just have to open Developer Tools.
I prepared demo which works in different way. It simulates sleep function using setInterval and does not block scripts execution. However, it involves some additional code.
Let's assume that we have initial code:
var arr = [0, 1, 2, 3, 4];
function step(val) {
console.log(val);
}
for (var i = 0, len = arr.length; i < len; i++) {
step(arr[i]);
}
Now, we'd like to rewrite it so that each log shows after one second:
var arr = [0, 1, 2, 3, 4],
steps = [];
function step(val) {
console.log(val);
}
for (var i = 0, len = arr.length; i < len; i++) {
steps[i] = step.bind(null, arr[i]);
}
var int = setInterval(function() {
var fun = steps.shift();
if(!fun) {
clearInterval(int);
return;
}
fun();
}, 1000);
Let me explain it a little bit. Firstly, I define steps array, where I put new functions with bound arguments. bind function basically creates new function with arguments which are bound to provided values. More details on MDN page.
Example:
function step(a) { console.log(a); }
var step1 = step.bind(null, 1);
// now step1 is like `var step1 = function() { console.log(1); }`
In for loop I create and put new functions using bind. The last step is to extract these functions from steps array, starting from beginning (using Array.prototype.shift method), and execute them with interval equal to 1 second.
I know it's not a direct solution of your problem, but I hope it helps you convert your code properly. If you decide to do so, I advise to convert code blocks within for and while loops to functions. It simplifies conversion a little bit.
You could try to use the debugging tools available on your browser. If you are on chrome, enable the developer tools by pressing Shift + Ctrl + I. If you are on firefox, you could download install the firebug extension. Once you have done this, you can step through your code by putting in place a breakpoint. This is done by putting the 'debugger;' keyword at the javascript point where you want to begin stepping through. E.g
sort(points);
debugger
for(...) {
message('Foo thing');
debugger;
while() {
message('Comparing points');
debugger;
}
}
Maybe you could wait for a button click and then when the click has happened you could step one line of code in?
set a onclick listener for the button and set the variable continue = true;
after the piece of code has executed and you want to wait for the next piece of code to run you could use.
//code just executed
while(continue == false) sleep(10);
continue = false;
//next code to be executed
while(continue == false) sleep(10);
continue = false;
//more code....
there is probably a better solution than this so don't take this code as the best unless its your only answer.

Categories

Resources