setTimeout doesn't work as expected - javascript

In my application I am loading user posts using the ajax scroll down feature.
The for loop iteration takes too much time, browser freezes until the results are displayed. So I implemented a setTimeout method to fix that, but for some reason the flow doesn't go inside the setTimeout method on debugging.
Also the page is blank, data is not rendered.
success : function(responseJson) {
$("#loadingdata").toggle();
enableScrolling();
if($.isEmptyObject(responseJson)){
$("#noMorePosts").toggle();
disableScrolling();
paginationComplete=true;
}
$.each(responseJson, function (index) {
(function(index) {
setTimeout(function(index) { //the flow doesn't move inside this
var resp_JSON=responseJson[index];
var dateObj=resp_JSON.postCreationTime;
resp_JSON.postCreationTime = moment(dateObj).format("h:mm a, ddd, MMM Do YY");
var timeago = moment(dateObj).fromNow();
resp_JSON.timeago = timeago;
resp_JSON.username=userName;
var post_template = $('#homepostcontainertemplate').html();
Mustache.parse(post_template);
var post_info = Mustache.to_html(post_template, resp_JSON);
$('#homepublisherpostsdiv').append(post_info);
$('div').linkify();
});
})(index);
});
When the flow reaches setTimeout the next code it hits is the jquery lib
Am I doing it right or missing something?
Note: I get the responseJson data from the server fine. Without the setTimeout the data is loaded on the page.

setTimeout takes an argument-less function (https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout), so having index as an argument is a little odd. I suspect that index is undefined so responseJson[index] is throwing out of bound exception (as evidenced by console.log(1) showing up as per Niloct's comment). If you change your code to:
$.each(responseJson, function (index) {
setTimeout(function() { // no index in the argument list
var resp_JSON=responseJson[index];
var dateObj=resp_JSON.postCreationTime;
resp_JSON.postCreationTime = moment(dateObj).format("h:mm a, ddd, MMM Do YY");
var timeago = moment(dateObj).fromNow();
resp_JSON.timeago = timeago;
resp_JSON.username=userName;
var post_template = $('#homepostcontainertemplate').html();
Mustache.parse(post_template);
var post_info = Mustache.to_html(post_template, resp_JSON);
$('#homepublisherpostsdiv').append(post_info);
$('div').linkify();
});
});
I suspect it will work.
(edited to take into account jjaulimsing's comment about not needing the encapsulating function.)

Related

Google Script: Play Sound when a specific cell change the Value

Situation:
Example Spreadsheet
Sheet: Support
Column: H has the following function "=IF(D:D>0;IF($B$1>=$G:G;"Call";"In Time");" ")" that changes the value depending on the result.
Problem:
I need to:
Play a sound when a cell in column H changes to "Call" on the sheet "Support".
This function will need to run every 5min.
Does the sound need to be uploaded to Drive or can I use a sound from a URL?
I will appreciate to anyone can help on it... I see a lot of code but I didn't understand very well.
This is a pretty tough problem, but it can be done with a sidebar that periodically polls the H column for changes.
Code.gs
// creates a custom menu when the spreadsheet is opened
function onOpen() {
var ui = SpreadsheetApp.getUi()
.createMenu('Call App')
.addItem('Open Call Notifier', 'openCallNotifier')
.addToUi();
// you could also open the call notifier sidebar when the spreadsheet opens
// if you find that more convenient
// openCallNotifier();
}
// opens the sidebar app
function openCallNotifier() {
// get the html from the file called "Page.html"
var html = HtmlService.createHtmlOutputFromFile('Page')
.setTitle("Call Notifier");
// open the sidebar
SpreadsheetApp.getUi()
.showSidebar(html);
}
// returns a list of values in column H
function getColumnH() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Support");
// get the values in column H and turn the rows into a single values
return sheet.getRange(1, 8, sheet.getLastRow(), 1).getValues().map(function (row) { return row[0]; });
}
Page.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<p id="message">Checking for calls...</p>
<audio id="call">
<source src="||a URL is best here||" type="audio/mp3">
Your browser does not support the audio element.
</audio>
<script>
var lastTime = []; // store the last result to track changes
function checkCalls() {
// This calls the "getColumnH" function on the server
// Then it waits for the results
// When it gets the results back from the server,
// it calls the callback function passed into withSuccessHandler
google.script.run.withSuccessHandler(function (columnH) {
for (var i = 0; i < columnH.length; i++) {
// if there's a difference and it's a call, notify the user
if (lastTime[i] !== columnH[i] && columnH[i] === "Call") {
notify();
}
}
// store results for next time
lastTime = columnH;
console.log(lastTime);
// poll again in x miliseconds
var x = 1000; // 1 second
window.setTimeout(checkCalls, x);
}).getColumnH();
}
function notify() {
document.getElementById("call").play();
}
window.onload = function () {
checkCalls();
}
</script>
</body>
</html>
Some sources to help:
Sidebars and Dialogs
Custom Menus
Simple Trigger - onOpen
`google.script.run.withSuccessHandler(callback).customFunction()
Array.prototype.map
Recursively calling checkCalls() eventually led to errors, when I implemented the main answer given (which is mostly correct and really useful, so thank you!).
// Note: But the original implementation would work fine for a while - say 90 minutes - then crash. The call that would normally take 1 second would take 300 seconds, and Execution would Halt. It looks like it blew the stack by keeping on recursively calling itself. When moved to a single call of check() with proper exiting of the function, it then worked.
The console log in Chrome on running the JavaScript, said this:
ERR_QUIC_PROTOCOL_ERROR.QUIC_TOO_MANY_RTOS 200
After much investigation, I worked out a better way of doing it... Which doesn't require recursion (and therefore won't blow the stack).
Remove this line:
// window.setTimeout(checkCalls, 500);
And use something like this - at the end of your script:
// This function returns a Promise that resolves after "ms" Milliseconds
// The current best practice is to create a Promise...
function timer(ms) {
return new Promise(res => setTimeout(res, ms));
}
async function loopthis () { // We need to wrap the loop into an async function for the await call (to the Promise) to work. [From web: "An async function is a function declared with the async keyword. Async functions are instances of the AsyncFunction constructor, and the await keyword is permitted within them. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains."]
for (var i = 0; i >= 0; i++) {
console.log('Number of times function has been run: ' + i);
checkCalls();
await timer(3000);
}
}
window.onload = function () {
loopthis();
}
</script>

How to slow down an Ajax call?

I have a function in JS contains a loop, that calls an AJAX call every iteration. The call to inserts checked elements into a DB and returns the results of those elements in the same page in the next section.
The problem I have is that when I check for e.g. 4 checkboxes out of 3 groupes, the only checkboxes of the last group gets added to the page. However, when I use alert(), I can see all elements.
I used setTimeout, but I got error in the code. I also added lines to give more time to AJX call, but the problem remains. So I wonder if there is a solution to slow down the code without using alert().
This is my script:
addAptitudeField : function(currentAutocompleteField, idChamp) {
var currentAutocompleteFieldBind = currentAutocompleteField;
var idChampBind = idChamp;
window.setTimeout(function() {
// Code ...
var paramDwr = {};
var newDivName = "div" + idChamp + lastValueId;
paramDwr[attributs.r_divId] = newDivName;
paramDwr[attributs.r_currentValue] = currentValue;
paramDwr[attributs.r_hiddenIdsField] = hiddenIdsField.id;
paramDwr[attributs.r_lastValueId] = lastValueId;
paramDwr[attributs.r_itemmod] = nbAptitudesCat % 2 == 0;
// setTimeout ( RepertoireDwr.ligneSuppEtSpanMessage, 1000 ) doesn't work
RepertoireDwr.ligneSuppEtSpanMessage(paramDwr, function(ajaxPage) {
divCategorie.update(divCategorie.innerHTML + ajaxPage.texte);
aptitudeAvecDetail.remetsValeursStockees();
var btnSuppression = $(newDivName).getElementsByTagName('img')[0];
btnSuppression.setAttribute("onclick", "formulaireFiche.updateCSS('" + newDivName + "');" + btnSuppression.getAttribute("onclick") + "fiche.updateCategorieSuppressionAptLieeUo(\'divCat" + currentCategorie + "\');"); });
}
//
// alert() : It works in this case.
//
// for (var i=0; i<5000000; i++) ; it doesn't work
}, 400);
}
Thank you in advance for your help and time.
I will likely be downvoted for mentioning this, because it is not a recommended procedure, but I believe every coder should have all facts.
In jQuery AJAX construct, there is option async:false, which will delay the script from continuing UNTIL the AJAX has completed processing. Needless to say, if things go wrong in the AJAX the browser could freeze. A lot depends on who your users are, and amount of traffic -- on a few of my ten-user in-house projects it was an acceptable solution.
$.ajax({
async: false,
type: 'post',
url: 'ajax/ax.php',
data: 'request=',
success: function(d){
if (d.length) alert(d);
}
});
Ref:
What does "async: false" do in jQuery.ajax()?
The better idea, however, is to look into the Promises interface, with methods like .when() and .then()
References:
https://jsfiddle.net/v86bc028/2/
http://jqfundamentals.com/chapter/ajax-deferreds#
http://digitizor.com/jquery-html-callback-function-using-promise/#
how does jquery's promise method really work?
The problem you're running into deals with asynchronous functions, or the A in AJAX. If you don't know what an asynchronous function is, there are many others who can explain it better than I can, so give that a google.
What's happening without the alert() in there is your code makes 4 sever calls, but all 4 get sent out before you get a response to any of them. With the alert() (or setTimeout), you're giving the code time to received each response to a call before the next one is made.
There are several ways you can approach this, the first way is by calling the next call after the first receives a response. The second way is to use an async function to call all 4 at once on different chains(?). I'm not the best at explaining this part, but there's plenty of code to be found on SO and online.
I think you have a more generic problem in your code, since you seem to need to delay your executions to wait till sth. else is finished, instead of getting anounced when it is done.
The line that annoys me most is this one
divCategorie.update(divCategorie.innerHTML + ajaxPage.texte);
what exactly is update doing? How is it implemented?
I assume it does sth. like divCategorie.innerHTML += ajaxPage.texte;
Wich is highly unfavorable, since the browser has to parse and rebuild, whatever there already is in divCategorie.innerHTML.
Just appending the new Markup would be better.
long way short: maybe a good hack would be to insert some hidden node as a placeholder (so you kan keep order, although the AJAX-requests may return in a different order) and replace that node with the real content, as soon as it arrives.
Kind of like this:
addAptitudeField : function(currentAutocompleteField, idChamp) {
var currentAutocompleteFieldBind = currentAutocompleteField;
var idChampBind = idChamp;
//this is done immediately, and therefore preserves the order of the loop,
//without any delays/timeouts
var placeholder = document.createElement("div");
placeholder.className = "placeholder";
placeholder.style.display = "none";
divCategorie.appendChild(placeholder);
window.setTimeout(function() {
// Code ...
var paramDwr = {};
var newDivName = "div" + idChamp + lastValueId;
paramDwr[attributs.r_divId] = newDivName;
paramDwr[attributs.r_currentValue] = currentValue;
paramDwr[attributs.r_hiddenIdsField] = hiddenIdsField.id;
paramDwr[attributs.r_lastValueId] = lastValueId;
paramDwr[attributs.r_itemmod] = nbAptitudesCat % 2 == 0;
// setTimeout ( RepertoireDwr.ligneSuppEtSpanMessage, 1000 ) doesn't work
RepertoireDwr.ligneSuppEtSpanMessage(paramDwr, function(ajaxPage) {
//convert the passed text into a DocumentFragment
var frag = fragment(ajaxPage.texte);
//replacing the placeholder with the fragment
divCategorie.insertBefore(frag, placeholder);
divCategorie.removeChild(placeholder);
aptitudeAvecDetail.remetsValeursStockees();
var btnSuppression = $(newDivName).getElementsByTagName('img')[0];
//this is also pretty horrible to me:
btnSuppression.setAttribute("onclick", "formulaireFiche.updateCSS('" + newDivName + "');" + btnSuppression.getAttribute("onclick") + "fiche.updateCategorieSuppressionAptLieeUo(\'divCat" + currentCategorie + "\');"); });
}
}, 400);
}
I think you should do some major refactoring. And take a look into Promises.
// * -> DocumentFragment
//strings/primitives are parsed as HTML-markup,
//null / undefined is ignored
//Arraylike structures are parsed recursively
var fragment = (function(container){
return function(src){
return reducer(document.createDocumentFragment(), src);
}
function reducer(frag, node){
var i, len, fc, c, r;
if(node === Object(node)){
if("nodeType" in node){
//dom nodes
frag.appendChild(node);
}else{
//Arraylike structures, like NodeLists or jQuery-Objects, or just plain Arrays
for(i = 0, len = ("length" in node && node.length)|0, r = reducer; i < len; (i in node) && r(frag, node[i]));
}
}else if(node != null) {
//strings (all primitives)
for((c=container).innerHTML = node; fc = c.firstChild; frag.appendChild(fc));
}
return frag;
}
})(document.createElement("div"));

Console output one time every 40 seconds with interval doesn't work

I'm trying to use Twitter API in order to retweet.And because Twitter has limitation to 2400 actions per day I decided to limit the retweet to one time every 40 seconds.
I'm using https://www.npmjs.com/package/twit using Streaming API.
My problem is that it continuously streams console.log instead of using setInterval.
By stream I mean it outputs console.log multiple times instead of one time.
function hastagRetweet() {
var stream = T.stream('statuses/filter', { track: ['#hastag']})
stream.on('tweet', function (tweet,error) {
var retweetId = tweet.id; // id
var retweetId_str = tweet.id_str;
var tweetTextRetweet = tweet.text; // tweet text
function twInterval() {
console.log('INFO ------- ',tweet.id);
console.log('INFO ------- ',tweet.text);
};
setInterval(twInterval, 1000 * 40);
});
}
Any way of getting the variales data retweetId,retweetId_str,tweetTextRetweet outside stream.on beside inserting them into a mysql table so that every 40 seconds it checks the variables fixed data and executes console.log?
There are multiple problems here:
You're trying to start an interval timer in an event handler. That means that every time the event fires you would be attempting to start a new interval timer and they would pile up.
Even if you did sucessfully start up an interval timer, each one would never change it's output since the variables in scope for it never change as it is started up within a given function and the arguments to that function are what they were when it was first called. Subsequent calls of the function will start a new function not change the arguments on the prior call of the function.
You aren't even starting your interval correctly. As it is, all you're doing is calling the function and passing it's return value to setInterval() which does nothing.
If the goal is just to output to the console each stream tweet event, then probably what you want is just this:
function hastagRetweet() {
var stream = T.stream('statuses/filter', { track: ['#hastag']})
stream.on('tweet', function (tweet,error) {
var retweetId = tweet.id; // id
var retweetId_str = tweet.id_str;
var tweetTextRetweet = tweet.text; // tweet text
console.log('INFO ------- ',tweet.id);
console.log('INFO ------- ',tweet.text);
});
}
You cannot get these variables outside the .on() handler. It's an async callback and the only place they reliably exist is within that handler.
If you can describe in more detail what the end result you're trying to achieve is, we can likely help more specifically. If you want to do something every 40 seconds, then maybe you need to collect data in some collection (probably an array) and then every 40 seconds evaluate what you have just recently collected.
Here's a method of collecting the events in an array and then evaluating them every 40 seconds:
function hastagRetweet() {
var stream = T.stream('statuses/filter', { track: ['#hastag']});
var tweets = [];
stream.on('tweet', function (tweet,error) {
tweets.push({id: tweet.id, str: tweet.id_str, text: tweet.text});
});
setInterval(function() {
// evaluate the tweets in the tweets array every 40 seconds
// do something with them
for (var i = 0; i < tweets.length; i++) {
// tweets[i].id
// tweets[i].str
// tweets[i].text
}
// reset the tweets array
tweets.length = 0;
}, 40 * 1000);
}
Please note that once you call hastagRetweet(), it will run forever.

Pass variable through asynchronous function

First of all, I'm using JQuery. Take a look:
$(document).ready(function() {
var btcusd = 600;
function getRate() {
$.get("rate.php", function(data) {
var btcArr = JSON.parse(data, true);
btcusd = btcArr["last"];
//This will give me the correct value
console.log(btcusd);
});
}
setInterval(function() {
//This will say 600 every time
console.log(btcusd);
//Update rate for next loop
getRate();
}, 3000);
});
Chrome console gives me the 600 every 3 seconds. If I do this manually in the chrome live console, I will get a real value, like 595.32.
Why does this not work like intended?
Thanks for help.
I think #Tobbe is quite on point here. One thing you can do to confirm, is to add something like console.log( btcArr ) and that should show you whether you're getting anything back.
I set up a not too different demo that should that once the ajax callback successfully updates the value, it never goes back to 600, showing that indeed the value does get changed in the callback and the new value is available outside the ajax callback.
The ajax code I used is:
function getRate() {
var gafa = "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=100&q=";
var url = gafa + encodeURI('http://news.yahoo.com/rss/sports/')+"&callback=?";
$.getJSON(url, function(data) {
//var btcArr = JSON.parse(data, true);
var xData = data.responseData.feed.entries
//btcusd = btcArr["last"];
btcusd = xData.length;;
//This will give me the correct value
console.log(btcusd);
});
}
The rest of the code is yours: WORKING JSFIDDLE DEMO

FirefoxOS: return array using device storage api

I am just getting started with coding for FirefoxOS and am trying to get a list of files in a directory.
The idea is to find the name of each file and add it to the array (which works), but I want to return the populated array and this is where I come unstuck. It seems that the array gets populated during the function (as I can get it to spit out file names from it) but when I want to return it to another function it appears to be empty?
Here is the function in question:
function getImageFromDevice (){
var imageHolder = new Array();
var pics = navigator.getDeviceStorage('pictures');
// Let's browse all the images available
var cursor = pics.enumerate();
var imageList = new Array();
var count = 0;
cursor.onsuccess = function () {
var file = this.result;
console.log("File found: " + file.name);
count = count +1;
// Once we found a file we check if there are other results
if (!this.done) {
imageHolder[count] = file.name;
// Then we move to the next result, which call the cursor
// success with the next file as result.
this.continue();
}
console.log("file in array: "+ imageHolder[count]);
// this shows the filename
}
cursor.onerror = function () {
console.warn("No file found: " + this.error);
}
return imageHolder;
}
Thanks for your help!
Enumerating over pictures is an asynchronous call. Essentially what is happening in your code is this:
You are initiating an empty array
You are are telling firefox os to look for pictures on the device
Then in cursor.onsuccess you are telling firefox os to append to the array you have created WHEN it gets back the file. The important thing here is that this does not happen right away, it happens at some point in the future.
Then you are returning the empty array you have created. It's empty because the onsuccess function hasn't actually happened.
After some point in time the onsuccess function will be called. One way to wait until the array is full populated would be to add in a check after:
if (!this.done) {
imageHolder[count] = file.name;
this.continue();
}
else {
//do something with the fully populated array
}
But then of course your code has to go inside the getImageFromDevice function. You can also pass a callback function into the getImageFromDevice function.
See Getting a better understanding of callback functions in JavaScript
The problem is with the aSynchronous nature of the calls you are using.
You are returning (and probably using) the value of imageHolder when it's still empty - as calls to the "onsuccess" function are deferred calls, they happen later in time, whereas your function returns immediately, with the (yet empty) imageHolder value.
You should be doing in this case something along those lines:
function getImageFromDevice (callback){
...
cursor.onsuccess = function () {
...
if (!this.done) {
// next picture
imageHolder[count] = file.name;
this.continue();
} else {
// no more pictures, return with the results
console.log("operation finished:");
callback(imageHolder);
}
}
}
Or use Promises in your code to accomplish the same.
Use the above by e.g.:
getImageFromDevice(function(result) {
console.log(result.length+" pictures found!");
});

Categories