Execution order bahaviour - javascript

I'm writing a function that upon call should populate the page with tiles. Data on tiles are acquired from the remote DB (hence the AJAX request). I'm also using jQuery 3.0 in the code.
Here is the function:
function populateDashboard() {
var tileBlueprint = '<div class="dashboard_tile">\
<div class="dashboard_tile_content">\
<table class="tile_table">\
<tr class="tile_title">\
<td>$title</td>\
</tr>\
<tr class="tile_data">\
<td>$value</td>\
</tr>\
</table>\
</div>\
</div>';
$.ajax({
url: 'http://' + AppVar.ServerUrl + '/restapi/Dashboard_GetData',
type: 'POST',
data: JSON.stringify({
'SessionId': AppVar.SessionId
}),
dataType: 'json',
contentType: "application/json",
success: function (data) {
if (data.ResultCode === '0') {
//current tiles get wiped
$('.dashboard_tile').fadeOut(100, function () {
$(".tile_handler").empty();
console.log("1 - " + Date.now())
});
//new tiles are parsed and data is injected into the string which represents the tile
//tiles are saved into an array
var json = $.parseJSON(data.TileData);
var tileArr = [];
$.each(json.tiles, function (index, element) {
tile = tileBlueprint.replace("$title", $.i18n("dashboard-" + element.title));
tile = tile.replace("$value", element.value);
tileArr[index] = tile;
console.log("2 - " + Date.now())
});
//now I loop trough the previously created array to populate the page
$.each(tileArr, function (index, element) {
setTimeout(function () {
$(element).hide().appendTo(".tile_handler").fadeIn(1000);
}, 1000 * index); //delay made longer to see the effect better
console.log("3 - " + Date.now())
});
} else {
ons.notification.alert($.i18n('error-retriving_data_problem'));
return;
}
},
error: function (request, error) {
ons.notification.alert($.i18n('error-conn_error'));
return;
}
});
}
I don't think the HTML where this is getting injected is relevat as that part is working fine.
The problem is that the fadeout and both each loops that get called upon success, get callout out of order. I tried to log the time at which each get executed and this is what I get:
//first run
2 - 1469707068268 (6 times)
3 - 1469707068269 (6 times)
//second run
2 - 1469707181179 (2 times)
2 - 1469707181180 (3 times)
2 - 1469707181181
3 - 1469707181181
3 - 1469707181182 (4 times)
3 - 1469707181183
1 - 1469707181283
1 - 1469707181284 (2 times)
1 - 1469707181285 (2 times)
1 - 1469707181286
I'm displaying 6 tiles, so comments 2 and 3 should fire 6 times and 1 only once.
Why doesn't 1 execute first?
Why is 1 executed 6 times? EDIT: Figured that one myself just now.
If 1 is executed last, why doesn't it delete all the tiles created before?
Another problem is that the first time it displays 6 tiles, but second (and onwards), it only displays 5 tiles (first is missing).
Anyone can help me explain what is going on and how can I avoid such bahaviour?
Thank you.

Why doesn't 1 execute first and why is 1 executed 6 times?
From the documentation for .fadeOut, the second parameter is "A function to call once the animation is complete, called once per matched element".
So in this case the function will be called after ~100ms (the delay you provide as the first parameter) and will be called six times (once for each matched element).
If 1 is executed last, why doesn't it delete all the tiles created before?
As seen above, 1 is run after 100ms. However, the actual nodes are added after 1000 * index ms:
setTimeout(function () {
$(element).hide().appendTo(".tile_handler").fadeIn(1000);
}, 1000 * index);
So for all but the first node the code actually appending the node is run after 1. However, for the first node (note: index of 0 => 1000 * 0 = 0ms delay), the appendTo code is run directly which means that it will in fact be removed when the .empty() is called after 100ms, which means you will only see 5 of the 6 nodes.
The solution to these problems is to somehow "synchronize" the code so that it runs in the way you expect it to. This is generally what callbacks are used for, you put the code you want to run after something is completed into the callback function. In this case, one solution could be to move the "adding" code into the fadeOut callback:
$('.dashboard_tile').fadeOut(100).promise().done(function () {
$(".tile_handler").empty();
var json = $.parseJSON(data.TileData);
var tileArr = [];
$.each(json.tiles, function (index, element) {
tile = tileBlueprint.replace("$title", $.i18n("dashboard-" + element.title));
tile = tile.replace("$value", element.value);
tileArr[index] = tile;
});
// ...
});
Notice the usage of .promise.done, which gives us a single callback once all elements have finished animating instead of one for each element.

I see multiple issues with your code so here is what I can recommend:
data: JSON.stringify({
'SessionId': AppVar.SessionId
}),
should just be
data: {'SessionId': AppVar.SessionId},
because jQuery's AJAX function will convert it for you.
Try console.log(data.TileData);; if you are already receiving a JS object/array then there is ZERO reason to call var json = $.parseJSON(data.TileData); so you should remove that.
Instead of
$.each(json.tiles, function (index, element) {`
use
$.each(data.TileData.tiles, function (index, element) {
Now for the final issue, fadeOut() and fadeIn() getting called out of order.
Try this:
// Make sure the fadeOut() finishes before calling more stuff!!!
//current tiles get wiped
$('.dashboard_tile').fadeOut(100, function () {
$(".tile_handler").empty();
console.log("1 - " + Date.now())
//new tiles are parsed and data is injected into the string which represents the tile
//tiles are saved into an array
var tileArr = [];
$.each(data.TileData.tiles, function (index, element) {
tile = tileBlueprint.replace("$title", $.i18n("dashboard-" + element.title));
tile = tile.replace("$value", element.value);
tileArr[index] = tile;
console.log("2 - " + Date.now())
});
//now I loop trough the previously created array to populate the page
$.each(tileArr, function (index, element) {
setTimeout(function () {
$(element).hide().appendTo(".tile_handler").fadeIn(1000);
}, 1000 * index); //delay made longer to see the effect better
console.log("3 - " + Date.now())
});
});

Related

Retrieving data from an object only works in the original function

I'm having issues retrieving data from an object using JS on my website. I have a third party scrape Instagram posts and provides JSON to my website via a link. I've managed to retrieve this data from the link and manipulate it, but the problem comes when I try to change the displayed image every 5 seconds.
I took the solution from How to change an image every 5 seconds for example? and tried to adapt for my solution, however, I get an error where posts[index] is undefined even though it shouldn't be.
posts = [];
let index = 0;
indexx = 0
$.getJSON('posts.json', function(data) {
$.each(data, function(i, f) {
posts[indexx] = f
indexx = indexx + 1
});
});
console.log(posts) // returns all the posts
window.onload = change();
function change() {
console.log(posts) // Returns the list of posts
console.log(posts[index]) // Returns 'undefined'
console.log(posts[1]) // Returns 'undefined'
$('#instaimg').attr('src', posts[index]["mediaUrl"])
if (index == 5) {
index = 0;
} else {
index++;
}
setTimeout(change, 5000);
}
I'm not sure if I am missing something or whether my lack of JS knowledge is to blame, but if anyone could help it would be appreciated
Several issues with your code:
Your console.log(posts) will show an empty array because the ajax callback has not finished yet => move that inside the .getJSON callback function
You call change recursively every 5 sec, e.g your call stack will grow indefinitely
Use setInterval instead of setTimeout
Start the interval timer inside the .getJSON callback function, so that it starts once the fetched data is ready
Use .push() to add to an array, no need to keep track of the index
Use $(function() { to make sure the DOM is ready before you do any action
You use a hardcoded length 4 for your data length, reference the array size instead
Updated code:
let index = 0;
let posts = [];
$(function() {
$.getJSON('posts.json', function(data) {
//$.each(data, function(i, f) {
// posts.push(f);
//});
// It looks like data is the array you want to use, so:
posts = data;
setInterval(changeImage, 5000);
});
});
function changeImage() {
$('#instaimg').attr('src', posts[index++]["mediaUrl"]);
if(index > posts.length) {
index = 0;
}
}

Use Javascript to refresh a window after a loop has run its course

I'm writing a news display for the company I work at and I'm trying to get the page to refresh after it's looped through the entire length of a JSON array. Currently everything works, but I'm not entirely sure where the refresh command would go. Where it is at the moment is not executing. Here's my relevant code:
var i = 0,
d = null,
x = null,
interval = 3000;
console.log('welcome');
$(document).ready(function(){
fetch();
console.log('fetching');
});
function fetch(){
// get the data *once* when the page loads
$.getJSON('info.json?ver=' + Math.floor(Math.random() * 100), function(data){
// store the data in a global variable 'd'
d = data[0];
console.log('results');
console.log(data);
// create a recurring call to update()
x = setInterval(function(){
update()
}, interval);
});
}
function update(){
console.log('update starting');
// if there isn't an array element, reset to the first once
if (d && !d[i]){
console.log('got the D');
clearInterval(x);
i = 0;
fetch();
return;
if(d[i] >= d.length){
// refresh the window if the variable is longer than the array
console.log('refreshing');
window.location.reload();
}
}
// remove the previous items from the page
$('ul').empty();
// add the next item to the page
$('ul').append(
'<li>' + d[i]['news']
+ '</li>'
);
// increment for the next iteration
i++;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='news'>
<ul></ul>
</div>
$.getJSON('info.json?ver=' + Math.floor(Math.random() * 100), function(data){
//your code
}).done(function( data ) {
window.location.reload();
});
Function written in done section will be executed after complete all code from main function
I have figured this out going in a direction I wasn't originally planning to. I realized that once I included an RNG for version control to my .json file I actually didn't need to refresh the page. I am using an iterative loop, so once the loop completes it goes back to the top, and re-opens the info.json. My RNG looks like this:
$.getJSON('info.json?ver=' + Math.floor(Math.random() * 100) + '.' + Math.floor(Math.random() * 100), function(data){
//code here
}
This means that every time $.getJSON is caled, it's looking at a different version number than it was before, which forces a server check and download, which keeps things in the file updating to the screen on their own. Network sources looked like this:
info.json?ver=1.23
info.json?ver=2.42
info.json?ver=1.15
Definitely not the solution I was looking for, but it is actually a better solution than my question could have asked.
If I understood you question, you just need to know where your reload function is correct?
Well it's window.location.reload();

How to loop through GET/POST calls sequentially (waiting for previous) return?

I'm writing a Tampermonkey script for a web page and trying to extract data from other pages.
I'm trying to make a function that has a loop inside that goes thru a list, llcList, and retrieves data from ajax method GET, but would like to wait for to finish one request before going to second one.
Bonus would be if I could make it wait some extra time.
What should happen:
send request for a llcList[0]
get return data, process it
wait some time
send new request for a llcList[1]
Is this possible? I tried few methods, every time loop send all requests not a second apart. :
function F_Company_LLC(){
for (i = 0; i < llcList.length;i++) {
if(llcList[i][2]=="lab"){
//run function 0
//break;
}
else if(llcList[i][2]=="shop"){
//run function 1
//break;
}
else{
F_GET_CompData(llcList, llcList[i][1],i,function(result){
console.log(result);
});
}
}}
function F_GET_CompData(F_GET_CompData_list, CompID, F_GET_CompData_row, callback){
$.ajax({
method : "GET",
url: base_link+"/company/edit_company/"+CompID,
beforeSend: function(){runningRequest++;},
success: function(data){
//data processing
runningRequest--;
},
error: function() {console.log("Get_ComData");}
});
callback(runningRequest);}
This is a common scenario. Note that it's often unnecessary to process the calls sequentially though. It's usually adequate to just send context with the ajax calls and piece everything together as it comes in semi randomly, as shown in this answer.
One way to force sequential behavior is to chain calls via the complete function. Here is fully functional code that demonstrates the process. To use, paste it into your browser console while on a Stack Overflow page. :
var listO_pages = ["q/48/", "q/27/", "q/34/", "q/69/", "badpage"];
var numPages = listO_pages.length;
getPageN (0); //-- Kick off chained fetches
function getPageN (K) {
if (K >= 0 && K < numPages) {
let targPage = listO_pages[K];
$.ajax ( {
url: "https://stackoverflow.com/" + targPage,
context: {arryIdx: K}, // Object Helps handle K==0, and other things
success: processPage,
complete: finishUpRequest,
error: logError
} );
}
}
function processPage (sData, sStatus, jqXHR) {
//-- Use DOMParser so that images and scripts don't get loaded (like jQuery methods would).
var parser = new DOMParser ();
var doc = parser.parseFromString (sData, "text/html");
var payloadTable = doc.querySelector ("title");
var pageTitle = "Not found!";
if (payloadTable) {
pageTitle = payloadTable.textContent.trim ();
}
var [tIdx, tPage] = getIdxAndPage (this); // Set by `context` property
console.log (`Processed index ${tIdx} (${tPage}). Its title was: "${pageTitle}"`);
}
function finishUpRequest (jqXHR, txtStatus) {
var nextIdx = this.arryIdx + 1;
if (nextIdx < numPages) {
var tPage = listO_pages[nextIdx];
//-- The setTimeout is seldom needed, but added here per OP's request.
setTimeout ( function () {
console.log (`Fetching index ${nextIdx} (${tPage})...`);
getPageN (nextIdx);
}, 222);
}
}
function logError (jqXHR, txtStatus, txtError) {
var [tIdx, tPage] = getIdxAndPage (this); // Set by `context` property
console.error (`Oopsie at index ${tIdx} (${tPage})!`, txtStatus, txtError, jqXHR);
}
function getIdxAndPage (contextThis) {
return [contextThis.arryIdx, listO_pages[contextThis.arryIdx] ];
}
This typically outputs:
Processed index 0 (q/48/). Its title was: "Multiple submit buttons in an HTML form - Stack Overflow"
Fetching index 1 (q/27/)...
Processed index 1 (q/27/). Its title was: "datetime - Calculate relative time in C# - Stack Overflow"
Fetching index 2 (q/34/)...
Processed index 2 (q/34/). Its title was: "flex - Unloading a ByteArray in Actionscript 3 - Stack Overflow"
Fetching index 3 (q/69/)...
Processed index 3 (q/69/). Its title was: ".net - How do I calculate someone's age in C#? - Stack Overflow"
Fetching index 4 (badpage)...
GET https://stackoverflow.com/badpage?_=1512087299126 404 ()
Oopsie at index 4 (badpage)! error Object {...
-- depending on your Stack Overflow reputation.
Important: Do not attempt to use async: false techniques. These will just: lock up your browser, occasionally crash your computer, and make debug and partial results much harder.

Protractor Wait for Animation to Complete

In our application, we have some questions to answer that will update a progress bar. Currently, I have a function that waits for HTML Attribute changes which works for most things, but it's a little finicky for the progress bar since the animation occurs over 1-2 seconds as the bar moves from 0 - 10% etc. So the failure I'm currently facing is things like: Expected 11 to be within range 12, 14.
Code:
Util.prototype.waitForAttributeChange = function (el, attr, time) {
var timeout = time || 0,
currentAttr;
el.getAttribute(attr).then(function (val) {
currentAttr = val;
return currentAttr;
}).then(function () {
return browser.wait(function () {
return el.getAttribute(attr).then(function (val) {
return val !== currentAttr;
});
}, timeout);
});
};
Usage:
Util.waitForAttributeChange(Page.progressBar(), 'style', 10000).then(function () {
expect(Page.getProgressBarValue()).toBeWithinRange(12, 14);
};
Problem: The value grabbed is not the end result of the progress bar, it's still moving when it's grabbing it (because my function waits for Attribute changes, and the attribute did change at this point)
Question: Is there another way I can wait for an animation, specifically waiting for it to be completed? And/or is this possible without using browser.sleep()?
You might be able to solve this problem by using Expected Conditions.
I use the below methods whenever I need to wait for an element to be visible then wait for it to go away before executing the next step. This is helpful for temporary confirmation modals that may block interaction with other elements.
let waitTimeInSeconds = 15;
let EC = protractor.ExpectedConditions;
secondsToMillis(seconds) {
return seconds * 1000;
}
waitToBeVisible(element: ElementFinder) {
browser.wait(EC.visibilityOf(element), this.secondsToMillis(waitTimeInSeconds), 'The element \'' + element.locator() + '\' did not appear within ' + waitTimeInSeconds + ' seconds.');
}
waitToNotBeVisible(element: ElementFinder) {
browser.wait(EC.not(EC.visibilityOf(element)), this.secondsToMillis(waitTimeInSeconds), 'The element \'' + element.locator() + '\' still appeared within ' + waitTimeInSeconds + ' seconds.');
}

For loop proceeding out of order

new at this, please tell me if I'm leaving information out or anything like that.
The code I'm working on can be seen here: http://codepen.io/hutchisonk/pen/mVyBde and I have also pasted the relevant section of javascript below.
I'm having trouble understanding why this code is behaving as it is. Quick outline - I have defined a few variables at the top, made a function that fetches the data I need and builds it into a pretty little list. This seems to be working as planned.
With the function outlined, I then loop through each "friend" in the "friends" array, calling the function once each time. I have numbered the friends on the output to help clarify what is going on. I have tried this a number of ways, including with the "for loop" syntax that's currently implemented, as well as the "forEach" syntax that's commented out.
Two main questions:
1) The number in front of each name is the "i" in my for loop. Why is this "i" not going in order from 0 to 10? How do I get it to do so? It appears to be in a different order every time the code is run. And, it repeats the numbers it has looped through previously on each new iteration. I would like to understand why this is happening.
2) The code seems to be running out of order. The unexpected behavior can be seen in the console.log - the for loop outputs the first two lines of console.log on a loop, then jumps out and console.logs the test variable "a" and the other text below the for loop, and then jumps back into the for loop and console.logs the output from the function. I'm looking at the console in google chrome and I did read that there can be timing inconsistancies with regard to the console, but I don't understand how the loop is being split in half - the first two lines, and then the function call being logged after the later code.
What is the best way to iterate through an array? Any insights on how to call a function within a loop correctly or resources you can provide are much appreciated.
$("document").ready(function(){
var friends = ["lainzero", "freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff", "dtphase", "MedryBW"];
var html = "";
var url = "";
function getStreamingData(eachfriend, number) {
url = "https://api.twitch.tv/kraken/streams/"+eachfriend;
$.ajax({
dataType: "jsonp",
url: url,
success: function(result) {
console.log(result+", "+result.stream);
if(result.stream !== null) {
html+= "<li class='streaming'><a href='twitch.tv/"+eachfriend+"'>"+number+": "+eachfriend;
html +="<i class='fa fa-play-circle style='font-size:20px;color:green;''></i>";
} else if (result.stream === null) {
html+= "<li class='not_streaming'><a href='twitch.tv/"+eachfriend+"'>"+number+": "+eachfriend;
html +="<i class='fa fa-stop-circle' style='font-size:20px;color:red;'></i>";
}
html +="</a></li>";
$("#all ul").append(html);
}//success
});//$ajax
}//getstreamingdata function
for (var i=0;i<friends.length;i++) {
console.log(i);
console.log(friends[i]);
getStreamingData(friends[i], i);
}
//Same as for loop above, but using forEach. This produces the same results.
/*
var i=0;
friends.forEach(function(friend) {
getStreamingData(friend, i);
i++;
});
*/
var a = 4;//testing console output
console.log(a);
console.log("why is this showing up before the getStreamingData function's console output?");
console.log("but it's showing up after the console.log(i) and console.lg(friends[i]) output? So this section is interupting the for loop above");
console.log(" and why is the for loop out of order and repeating itself?");
});//doc ready
You are doing an asynchronous task in your loop. You should not expect those async tasks finish in the order that they have started.
The function getStreamingData is the one that I'm talking about.
Related: Asynchronous for cycle in JavaScript
This is one snippet that I wrote long time ago and I'm still using it in small projects. However there are many libraries out there which do the same plus many more.
Array.prototype.forEachAsync = function (cb, end) {
var _this = this;
setTimeout(function () {
var index = 0;
var next = function () {
if (this.burned) return;
this.burned = true;
index++;
if (index >= _this.length) {
if (end) end();
return;
}
cb(_this[index], next.bind({}));
}
if (_this.length == 0) {
if (end) end();
}else {
cb(_this[0], next.bind({}));
}
}, 0);
}
It is not a good practice to touch the prototype like this. But just to give you an idea how you can do this ...
After that code, you can loop over arrays asynchronously. When you are done with one element, call next.
var array = [1, 2, 3, 4]
array.forEachAsync(function (item, next) {
// do some async task
console.log(item + " started");
setTimeout(function () {
console.log(item + " done");
next();
}, 1000);
}, function () {
console.log("All done!");
});

Categories

Resources