This might be a newb question but....
Recently, I have been using window.setTimeout which makes a recursive call to the parent function, which makes an ajax poll to the server.
function connectToVM(portal) {
//making the ajax call here....
if(response.responseText !== "")
{
windowInterval = window.setTimeout(function() {
connectToVM(portal)
}
, 4000);
}
}
windowInterval is my global var here.
if(!checkIfChartExists()) {
window.clearInterval(windowInterval);
}
Now, instead of making use of variables here, I know that I can simple pass the function to clearTimeout, but that also causes all the other intervals to be stopped :(
The reason why I am doing this is the server does a timeout, only when there is a response.
My scenario is, I have a chart which updates every timeout interval.
AFAIK, when we set the interval, there is a specific value set to the variable(if set to a variable). So when I print my variable(every time when the timeout function is called), I get some int values which are unique.
I have many tabs, and many tabs can have same chart.. which just makes use of the same interval which is triggered earlier.
Now I have just 1 chart.. But I have many charts to show which are of the same type. Say gauge Chart.
I also have to clear the timeout whenever there is no chart present in the current selected tab - which I am doing.
So I am planning to make just 1 function which just makes the call to the server by passing in the required params to this function.
But in order to poll, I am using window.setTimeout thing I mentioned above.
This works for 1 chart.
Now, I try to add 1 more chart, with different set of params to poll the server, I will need to make use of some different setTimeout function, which has a id different than that of the earlier triggered timeout.
I also have to consider that if the 1st chart is already present, the timeout is already triggered and have to keep it running.
So, now I have to trigger the second timeout.
But there is no second timeout here.
I was wondering if there is any alternate approach to this, as I can't really predict how many chart's there will be on runtime.
Question 1 : Can we flood our browser with many timeout's?
Question 2 : How to get the id of that particular timeout, so that I can clearTimeout(id) on it?
Question 3 : Since we can't assign / make variables on the fly, how to set / make such a data structure which can hold such a pointer to the particular chart's index / id.. so that we can easily get hold of it and clear it.
Question 4 : Is this the only way we can poll the server(via AJAX) if we have to poll continually?
Lastly, I recognize this is a very complex issue I have posted in here. But I am sure I will find some useful information about the approach from the forums.
I don't have much experience doing all these stuffs in JS, but any help is appreciated!
Update
Sorry I have to post my code in here.. But I am using Extjs to get my chart portlets. My code for the function connectToVM is this :
function connectToVM(portalId, host, port, user, passwd, db) {
try{
if(Ext.getCmp(portalId))
{
var gaugeChartForTitle = Ext.getCmp(portalId);
if(typeof portalOriginalTitle === 'undefined')
portalOriginalTitle = gaugeChartForTitle.title;
var gaugeChartDiv = document.getElementById(portalId);
Ext.Ajax.request({
url: "/connectToVM?" +
Ext.urlEncode({
host: host,
port: port,
user: user,
passwd: passwd,
db: db
}),
method: 'GET',
success: function (response, options) {
if(response.responseText !== "")
{
gaugeChartDiv.style.background = "";
gaugeChartForTitle.setTitle(portalOriginalTitle);
console.log("Virtual Machine at "+ host +" : BUSY % : "+response.responseText);
virtualMachineStore.loadData(generateVirtualMachineData(response.responseText)); //Setting the data1 value of the store and loading it for display!
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 4000);
}
else
{
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 10000); //Retry every 10 seconds to check if the connection is established!
gaugeChartDiv.style.background = "red";
gaugeChartForTitle.setTitle(portalOriginalTitle +" - Connection Failure. Reconnecting!");
}
},
failure: function ( result, request) {
}
});
}
}
catch(err) {
}
}
Now, I trigger my function using this :
function setWindowIntervalForVM(portalId) {
//console.log("isIntervalActivated inside setWindowIntervalForVM() : "+isIntervalActivated);
if(!isIntervalActivated) {
connectToVM(portalId, host, port, user, pwd, db);
}
}
function checkIfWindowIntervalIsActivated(portal) {
if(!isIntervalActivated) {
setWindowIntervalForVM(portal.id);
isIntervalActivated = true;
} else {
window.clearInterval(windowInterval);
windowInterval = null;
isIntervalActivated = false;
}
}
So checkIfWindowIntervalIsActivated() is my parent function call which I call in these scenarios :
1) Whenever the Gauge Chart is newly created.. I Trigger this call and have a boolean isIntervalActivated which if it is false, triggers the server poll.
2) So now if I have the chart already in tab 1(since the user selected it), I now change to tab 2 which does not have it. So I simply set isIntervalActivated to true which stops the poll. This is handled for 1 chart. Now the question here is, if I want to make this function re-usable, say I want to drop one more chart of same type but with different server parameters to poll, how to make use of the same windowInterval variable which has my 1st chart's triggered timeout value. P.S: The value changes for every ajax request it makes. So there'z no 1 single value :(
3) I stop the poll whenever there is no chart of same type present.. in other tab. which makes perfect sense. Now, I am caching all my portlets whenever user drops in a new portlet / on the page load, pulling all the user configured portlets. In such a case, I have to trigger all of the charts' ajax calls.. each polling to its configured destination. Now, I do not know how many charts there will be, as in my function name, I am polling to VM's. So if the user consumes VM1, it switches to VM2 and so on.
So it's absolutely impossible to just create same function for many such similar charts.
So just wanted to check if I can re-use the same timeOut thing, or take a totally different approach to this problem :( :(
I hope it's a bit clear now, if not I can explain my situation more.
Please ask me more questions if required :)
Thanks again!
If I understood correctly and you're trying to support multiple charts updating concurrently, I'd switch from keeping the chart data inside the connectToVM() closure to an explicit array of chart objects and use a single interval to update all charts.
Something like the following (treat it as pseudo-code):
var charts = [
// an array of chart objects, see addChart()
];
function addChart() {
// when you need to add or remove a chart, update the charts object, like this:
charts.push({
update: updateChart,
nextUpdateTime: null, // or Date.now() if you don't care about old browsers.
chartData: {host: ..., port: ..., user: ..., passwd: ..., db: ...,
originalTitle: ..., portalId: ...},
});
restartUpdates();
}
var activeInterval = null;
function restartUpdates() {
if (activeInterval) {
clearInterval(activeInterval);
}
activeInterval = setInterval(updateCharts, 5000);
}
// updates all active charts
function updateCharts() {
var now = new Date().getTime();
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
if (chart.nextUpdateTime !== null && chart.nextUpdateTime < now) {
chart.nextUpdateTime = null; // chart.update() will re-set this
try {
chart.update(chart);
} catch(e) {
// handle the error
}
}
}
// update a single chart.
// #param |chart| is an item from the |charts| array.
function updateChart(chart) {
// ...same as your connectToVM() using properties from chart.chartData...
Ext.Ajax.request(
// ...
success: function (response, options) {
// ...same as before...
// ...but instead of re-setting the timeout:
// windowInterval = window.setTimeout(function() {
// connectToVM(portalId, host, port, user, passwd, db)
// }
// , 4000);
// ...update the chart's nextUpdateTime:
chart.nextUpdateTime = (new Date().getTime()) + 4000;
}
);
}
initial answer below
Thanks for the detailed question! It feels you're missing something very obvious wrt questions #2/3, but it's hard to tell what specifically without seeing more of your code. Can you post a more complete, yes simple example demonstrating the problem you're trying to solve? Perhaps the function handling changing the active tab in pseudocode would help, like this:
function selectTab(tabID) {
// ...activate tab #tabID in the GUI...
if (tabID == 1) {
// there's chart #1 on tab #1, need to stop any active timeouts and start a new one
connectToVM("chart #1");
} else if (tabID == 2) {
// no charts on tab #2.. need to stop any active timeouts
} else if (tabID == 3) {
// ...
}
}
One thing I don't understand is whether there's always a single chart, that needs updating, at any point of time?
Also, do you know the concepts mentioned in A re-introduction to JavaScript, specifically objects?
As for the questions:
1: yes, too many timeouts should be avoided (thousands a second will probably make the CPU hot and the browser sluggish), although I'd be more worried about the server, which has to handle the requests from multiple clients.
2/3: see above.
4: The Comet page lists a lot of alternatives to basic AJAX polling (server-sent events, long-polling, websockets), but I'd worry about this later.
Yes
var x = window.setTimeout(...); window.clearTimeout(x);
Store it as a data attribute on the active tab, a property on your object, or as a global variable. Many different ways. Example code would have made it easier to answer this.
Based on your comments:
var windowInterval;
function connectToVM(portal) {
if(windowInterval)window.clearTimeout(windowInterval);
windowInterval = window.setTimeout(function() { ... }, 4000);
}
Related
I am wondering how i can solve the following issue. I have a Record in the Firebase DB which i am monitoring. The App is a Sports Score So far so good.
When user loads the initial page i check if Game is Running or Stopped and so a few things.
below is a snipped of what i do
if(Clock.Status=='Running'){
......
}
else if(Clock.Status == 'Stopped'){
.......
}
So far so good when user hits the page for first time. But now i want to monitor if the ClockStatus changes
clockStatusRef = firebase.database().ref("games/"+gameId+"/Score/");
clockStatusRef.on("child_changed", function(snapshot) {
var Clock = snapshot.val();
var status = Clock.Status;
// clock stopped - second scenario
if(status=='Stopped'){
stopTimer();
}
else if(status == 'Running'){
// clock status running- third scenario
firebase.database().ref('/.info/serverTimeOffset')
.once('value')
.then(function stv(data) {
console.log('hi');
serverTime = (data.val() + Date.now())/1000;
var timeElapsed = serverTime - Clock.ClockStart;
var totalCounts = document.getElementById("total_counts");
if(Clock.Timer > timeElapsed){
initTimer(Math.floor(Clock.Timer- timeElapsed),60);
}
else{
var Current_Clock = document.getElementById("count");
Current_Clock.innerHTML = '00:00';
}
}, function (err) {
return err;
});
}
console.log("Clock status changed: "+status);
});
for some strange reason on a change of status it starts with the main if
if(Clock.Status=='Running')
So i am wondering what am i missing and what is the best way to fix this so the first if is only run on the initial load and all subsequent will use the if's which handle status change of clock.
Here is the Json for games/B8120ACD-DF51-A64A-A83E-556007522E80/Score/Clock
{
"ClockStart" : 1510535632,
"Period" : 1,
"Status" : "Stopped",
"Timer" : 900
}
You're listening one level higher in your JSON than your code expects.
Either change the code that gets the clock from the snapshot to:
var Clock = snapshot.val().Clock;
Or (better, because it requires less data transfer) listen one level lower in the tree:
clockStatusRef = firebase.database().ref("games/"+gameId+"/Score/Clock");
As there seem to be some limitation as far as what is triggered when multiple listeners looking for changes and data overlaps i changed my code. As my App does not have any heavy traffic so changes are not that often, i use one listener for changes and to address my issue i just went ahead and added the run once for the initial setup then run different code on updates. Would have been nice to control what listener gets the notification of change, also the child_changed seems to have its limitations as i got it to fire but was not able to tell which child actually changed.
I have this javascript code where the setInterval() is triggered every 2 sec to update the var kraken_btc_eur
However sometimes the variable retrieved from the API does not change. Therefore, to save some serveur processing I would like to avoid the setInterval action to be triggered .
Maybe what I am asking does not make sense, it is just a though for optimisation.
Thank you for your guidance.
var kraken_btc_eur_old = 0;
setInterval(function(){
$.get('https://api.kraken.com/0/public/Ticker?pair=XXBTZEUR', function(data){
var kraken_btc_eur = data.result.XXBTZEUR.c[0]; //get the value of 1 bitcoin
//some logic to change the css if the value increased or decreased
if (kraken_btc_eur_old > kraken_btc_eur) {
$(".kraken_btc_eur").css('color', 'red').text(kraken_btc_eur);
} else {
$(".kraken_btc_eur").css('color', 'green').text(kraken_btc_eur);
}
kraken_btc_eur_old = kraken_btc_eur; //set the global variable to the value of 1 bitcoin so that in 2 sec it will be checked in the if statement
$(".kraken_btc_eur").text(kraken_btc_eur); //show the value to the user to the html tag with the corresponding class
});
}, 2000);
With setInterval, you are using an approach called short-polling. This is when you continuously request data from the server to determine if anything changed.
There are two major alternatives to short-polling. Of these, I believe you are looking for WebSockets, which are essentially Sockets that you can use with JavaScript. WebSockets allow you to pass unformatted data from the client to the server, and vice versa. Using WebSockets, your server would have to keep an open socket to the client, but would only send data to the client if something changed on the server's side.
Of course, this is assuming you are the developer of the API.
If not, you'll have to stick to short polling. You could have an approach where if the value from the API doesn't change for a while, you could decrease the frequency of your short polls - but that would require you to switch from setInterval to setTimeout. In your callback, you could determine what the new timeout will be between the current callback and the next callback.
This approach would look something like the following:
setTimeout(function callback(timeout){
// .. get request here
if(timeout !== undefined)
{
if(kraken_btc_eur == kraken_btc_eur_old)
{
timeout = Math.min(10000, timeout + 1000);
}
else
{
timeout = 2000;
}
}
else
{
timeout = 2000;
}
setTimeout(callback.bind(window, timeout), timeout);
}, 2000);
I believe kraken api has an etag header not so sure. You can check if the etag is the same with the old etag means the data hasn't change.
Some reference.
http://www.websiteoptimization.com/speed/tweak/etags-revisited/
https://www.infoq.com/articles/etags
If you have the option of setting up a websocket its better. Check https://socket.io/.
You can make few changes in your code
var window.kraken_btc_eur_old = 0;
function makeRequest(oldValue) {
$.get('https://api.kraken.com/0/public/Ticker?pair=XXBTZEUR',
function(data){
var kraken_btc_eur = data.result.XXBTZEUR.c[0];
if(kraken_btc_eur !== oldValue){
//get the value of 1 bitcoin
//some logic to change the css if the value increased or decreased
if (oldValue > kraken_btc_eur) {
$(".kraken_btc_eur").css('color', 'red').text(kraken_btc_eur);
} else {
$(".kraken_btc_eur").css('color', 'green').text(kraken_btc_eur);
}
window.kraken_btc_eur_old = kraken_btc_eur;
//set the global variable to the value of 1 bitcoin so that in 2 sec it will be checked in the if statement
$(".kraken_btc_eur").text(kraken_btc_eur); //show the value to the user to the html tag with the corresponding class
}
});
}
setInterval(makeRequest.bind(null,window.kraken_btc_eur_old), 2000);
I have written window.kraken_btc_eur_old just to give you an understanding that it's a global object
Using jQuery I'm writing a website api call in Javascript, which so far works pretty well. When a person updates a number in a text input it does a call to the API and updates a field with the response. It gets problematic however, when I user quickly makes a lot of changes. The javascript then seems to pile up all queries, and somehow does them side by side, which gives the field to be updated kind of a stressy look.
I think one way of giving the user a more relaxed interface, is to only start the API call after the user finished editing the input field for more than half a second ago. I can of course set a timeout, but after the timeout I need to check if there is not already a call under way. If there is, it would need to be stopped/killed/disregarded, and then simply start the new call.
First of all, does this seem like a logical way of doing it? Next, how do I check if a call is underway? And lastly, how do I stop/kill/disregard the call that is busy?
All tips are welcome!
[EDIT]
As requested, here some of the code I already have:
function updateSellAmount() {
$("#sellAmount").addClass('loadgif');
fieldToBeUpdated = 'sellAmount';
var buyAmount = $("#buyAmount").val();
var sellCurrency = $("#sellCurrency").val();
var buyCurrency = $("#buyCurrency").val();
var quoteURL = "/api/getQuote/?sellCurrency="+sellCurrency
+"&buyAmount="+buyAmount
+"&buyCurrency="+buyCurrency;
$.get(quoteURL, function(data, textStatus, jqXHR){
if (textStatus == "success") {
$("#sellAmount").val(data);
$("#sellAmount").removeClass('loadgif');
}
});
if (fieldToBeUpdated == 'sellAmount') {
setTimeout(updatesellAmount, 10000);
}
}
$("#buyAmount").on("change keyup paste", function(){
updateSellAmount();
});
If you make your AJAX call like this:
var myAjaxDeferred = $.ajax("....");
You can check it later with:
if (myAjaxDeferred.state() === "pending") {
// this call is still working...
}
I need to keep track of a counter of a collection with a huge number of documents that's constantly being updated. (Think a giant list of logs). What I don't want to do is to have the server send me a list of 250k documents. I just want to see a counter rising.
I found a very similar question here, and I've also looked into the .observeChanges() in the docs but once again, it seems that .observe() as well as .observeChanges() actually return the whole set before tracking what's been added, changed or deleted.
In the above example, the "added" function will fire once per every document returned to increment a counter.
This is unacceptable with a large set - I only want to keep track of a change in the count as I understand .count() bypasses the fetching of the entire set of documents. The former example involves counting only documents related to a room, which isn't something I want (or was able to reproduce and get working, for that matter)
I've gotta be missing something simple, I've been stumped for hours.
Would really appreciate any feedback.
You could accomplish this with the meteor-streams smart package by Arunoda. It lets you do pub/sub without needing the database, so one thing you could send over is a reactive number, for instance.
Alternatively, and this is slightly more hacky but useful if you've got a number of things you need to count or something similar, you could have a separate "Statistics" collection (name it whatever) with a document containing that count.
There is an example in the documentation about this use case. I've modified it to your particular question:
// server: publish the current size of a collection
Meteor.publish("nbLogs", function () {
var self = this;
var count = 0;
var initializing = true;
var handle = Messages.find({}).observeChanges({
added: function (id) {
count++;
if (!initializing)
self.changed("counts", roomId, {nbLogs: count});
},
removed: function (id) {
count--;
self.changed("counts", roomId, {nbLogs: count});
}
// don't care about moved or changed
});
// Observe only returns after the initial added callbacks have
// run. Now return an initial value and mark the subscription
// as ready.
initializing = false;
self.added("counts", roomId, {nbLogs: count});
self.ready();
// Stop observing the cursor when client unsubs.
// Stopping a subscription automatically takes
// care of sending the client any removed messages.
self.onStop(function () {
handle.stop();
});
});
// client: declare collection to hold count object
Counts = new Meteor.Collection("counts");
// client: subscribe to the count for the current room
Meteor.subscribe("nbLogs");
// client: use the new collection
Deps.autorun(function() {
console.log("nbLogs: " + Counts.findOne().nbLogs);
});
There might be some higher level ways to do this in the future.
I recently ran into a familiar javascript/jQuery timing bug and spent too long debugging it. What I need is a smarter debugging path for this problem.
In specific, my issue was that user inputs were supposed to be causing a Mongo database call and the results were sent, after a little math, to displayed outputs. But the displayed outputs were crazily wrong. However, once I added a FireBug break point the problem went away. At that point I knew I had a timing issue, but not how to solve it.
Here are the relavant pieces of code before the error:
handleDataCallBack : function(transport) {
var response = $.parseJSON(transport);
if(!hasErrors) { this.updatePage(response); }
},
accessDatabase : function(){
var params = { ... };
DAL.lookupDatabaseInfo(this.handleCallBackOutputPanel, this, params);
},
calculateValues: function() {
// some numerical values were updated on the page
}
onDomReady : function() {
// ...
//bind drop-down select change events
$('#someDropDown').change(function() {
me.accessDatabase();
me.calculateValues();
});
}
To fix the problem, all I had to do was move the "calculateValues" method from the onDomReady inside the call back:
handleDataCallBack : function(transport) {
var response = $.parseJSON(transport);
this.calculateValues();
if(!hasErrors) { this.updatePage(response); }
},
The problem was that the database hadn't responded before the calculations were started. Sure, that's easy to spot in retrospect. But what methods can I use to debug asynchronous timing issues in javascript/jQuery in the future? This seems well outside the context of IDE tools. And FireBug didn't help. Are there any tools for tracking down asynchronous web development issues? Or maybe some time-tested methods?
i assume your problem is caused here:
$('#someDropDown').change(function() {
me.accessDatabase();
me.calculateValues();
});
this issue is that your calculations are done just right after the call. seeing that the DB call is async, calculate does not wait for it. however, you can do it using "callbacks". i see you do try to implement it and yes, it is correct. however, i find this more elegant:
calculateValues: function() {
// some numerical values were updated on the page
},
//since this is your general callback hander
//you hand over the return data AND the callbackAfter
handleDataCallBack: function(transport, callbackAfter) {
var response = $.parseJSON(transport);
//you may need to use apply, im lost in scoping here
callbackAfter();
//or
callbackAfter.apply(scope);
if (!hasErrors) {
this.updatePage(response);
}
},
accessDatabase: function(callbackAfter) {
var params = {};
//pass callbackAfter to the function,
//after this is done, pass it to the handler
DAL.lookupDatabaseInfo(this.handleCallBackOutputPanel, this, params, callbackAfter);
},
onDomReady: function() {
$('#someDropDown').change(function() {
me.accessDatabase(function() {
//send over what you want done after.
//we'll call it "callbackAfter" for easy tracing
me.calculateValues();
});
});
}