I'm somewhat new to JS and I'm trying to think of the best way to design some asynchronous interaction in an application I'm working on.
I've got a list of records that are related to some live API-backed data. I show the user a list of these records, and the user can select specific records that they want to see more information about. I load this additional data from the API via an ajax call.
To make this a more real world example, let's say what I have is a list of stocks. I've got the name and yesterday's closing price for each stock. There's a check box next to each stock name, and if the user checks this box it plots the historic price of the stock for the past year on a graph.
When the user selects one stock in the way, the behavior is simple. I send one API request for the historical data for one stock, and I plot it on the graph.
However, the user might select a bunch of stocks at once, or in rapid succesion. I don't want to fire 10 or 20 or 50 requests back-to-back, I want to make one request for 10 or 20 or 50 stock histories.
Let's say my application has an event listener that looks up the stock history when the check box is toggled, something like this:
$('input.stock_toggle').change( function(event){
var symbol = $(this).data('symbol');
lookupStockHistory(symbol);
});
How could I define a lookupStockHistory function, or some other kind of event listener etc., that would wait a second and pool all the events that came in to send a single request instead of firing many times in row?
var lookupStockHistory = (function () {
"use strict";
var qeue = [], timeoutHandler = null, timeoutTime = 1000,
sendCall = function () {
//process qeue array and trigger ajax call
//and clean qeue
qeue = [];
},
add = function (symbol) {
if (timeoutHandler) {
clearTimeout(timeoutHandler);
timeoutHandler = null;
}
qeue.push(symbol);
timeoutHandler = setTimeout(sendCall, timeoutTime);
};
return add;}());
To trigger just call lookupStockHistory(symbol). This will gather symbol to array which will be processed after 1 second since last call
You can use push your request into a "global variable" with your namespace, and then use setTimeout to delay the AJAX call (a second or two maybe?).
The setTimeout would call a function that gets the requests from the "global variable", empties the variable, and then constructs your request. Any subsequent calls to the setTimeout function would see that the "global variable" was empty and not construct future AJAX requests.
In the example below, I also remove the current pending timeout as a new one has been initiated.
Here's a pseudo-code example using jQuery for selection and event capture:
var DELAY_FOR_INPUT = 2000; // 2 seconds
var g_MyDataToRequest = [];
var g_currentAJAXCallTimeout = null;
function _callAPI = new function() {
g_currentAJAXCallTimeout = null;
var dataToGet = g_MyDataToRequest;
g_MyDataToRequest = []; // clear out global request cache
// TODO: Loop over dataToGet and construct AJAX request
// TODO: Perform AJAX call...
}
$('.myCheckbox').click(function() {
var myRequest = $(this).attr("ID"); // or Val(), or whatever you'd like to track your item
g_MyDataToRequest.push( myRequest );
// If have a pending request, kill it
if (g_currentAJAXCallTimeout != null) {
clearTimeout(g_currentAJAXCallTimeout);
g_currentAJAXCallTimeout = null;
}
g_currentAJAXCallTimeout = setTimeout( _callAPI, DELAY_FOR_INPUT );
});
This is, as noted, pseudocode and may not quite work right, but it should get you going.
You could implement a timer and start it with the first click or change event. And with each additional click or change event you can reset the timer. Also, with each event you can add or remove the symbol value to an array accordingly. Once the timer expires, you join the array to be a comma-delimited string and post that back via ajax and get a JSON result.
Related
I am currently using D3.js and have modified my chart from listening to mouseover/mouseout to mousemove. This has brought quite a few issues in the chart but none moreso than my GET statuses/show/:id requests.
Previously, I would have points on my chart to hover over and if there was a tweet within half an hour of that point (from a tweet DB in backend), it would send a GET request to get that tweet.
My problem now is that because I'm using mousemove in proximity to these points on my chart as opposed to mouseover, its firing this hundreds of times and the GET requests are limited to 900 in a 15-minute window.
var tweet_arr = [];
for(j in data_tweets){
var tweet_time = timeParser(data_tweets[j]['timestamp_s']);
var point_time = timeParser(d.timestamp);
var diff = point_time.getTime() - tweet_time.getTime();
if(diff<=1800000 && diff>=-1800000) {
tweet_arr.push(data_tweets[j]);
} else {
var tweet_list = []
d3.selectAll(".panel-body")
.data(tweet_list)
.exit()
.remove();
}
}
twitterapi.fetch().getTweets(tweet_arr, tweet_urls[0], tweet_urls[1]);
This function checks the difference between the nearest point on the x-axis and checks my collection of tweet data, if there is one in half an hour, add it to an array called tweet_arr and then pass that into my fetch() function which has an AJAX call to the Flask framework where I run my GET request by ID.
What I would ideally want it to do is have some check that if the request to fetch a specific tweet has been carried out in say, the last 5 seconds, don't run the fetch() function.
How would I go about doing something like this?
Have a look at debounce and throttle from underscore.js: http://underscorejs.org/#debounce,
http://underscorejs.org/#throttle
Here's a good, short post about debouncing requests: https://www.google.de/amp/s/davidwalsh.name/javascript-debounce-function/amp
For a comparison between throttle and debounce, see https://gist.github.com/makenova/7885923
You need to define your fetch logic in a separate function and put that one into _.debounce.
Have a look at this example: https://codepen.io/anon/pen/EQwzpZ?editors=0011
const fetchFromTwitter = function(s) { console.log(s) }
var lazyFetch = _.debounce(fetchFromTwitter, 100)
lazyFetch('This is')
lazyFetch('is')
lazyFetch('gonna be')
lazyFetch('legen ... ')
lazyFetch('wait for it')
lazyFetch('... dary')
lazyFetch('LEGENDARY')
I'm attempting to create a stack of AJAX responses in BaconJS. That processes them in a first in first out fashion, but each 'out' event should wait for user input.
This is where I'm at now: Live JSBin
var pages = Bacon.fromArray([1,2,3])
var next = $("#next").asEventStream('click').map(true);
pages.flatMapConcat(asyncFunction).zip(next).log("responses")
function asyncFunction(page) {
// Simulating something like an AJAX request
return Bacon.later(1000 + (Math.random() * 3000), "Page "+ page)
}
Currently this synchronously outputs an event from the pages EventStream each time that #next is clicked, which the behavior I want.
However, I am unable to figure out how to push more values to the pages EventStream. I have attempted to replace the pages EventStream with a Bus, and pushing values like this (which doesn't work).
var pages = new Bacon.Bus()
pages.push("value")
How do I push more values to an EventStream?
I know this is an OLD post, but bus would work. Just push the number (you had an array of numbers before) into it:
var pages = new Bacon.Bus();
// Bacon.fromArray([1,2,3])
var next = $("#next").asEventStream('click').map(true);
pages.flatMapConcat(asyncFunction).zip(next).log("responses")
function asyncFunction(page) {
// Simulating something like an AJAX request
return Bacon.later(1000 + (Math.random() * 3000), "Page "+ page)
}
pages.push(1);
pages.push(2);
pages.push(3);
I cloned your jsbin and changed it to use bus
As mentioned previously, you could stream the source of the page values using something like fromEvent or from fromBinder
The YouTube API takes a very short amount of time for a request to its server to come back (In my case, I'm sending a search query). However, it's still too slow to make my program synchronous.
This is the Search Bar handler:
Template.search_bar.events({
'keypress #query' : function (evt,template) {
// template data, if any, is available in 'this'
if (evt.which === 13){
var url = template.find('#query').value;
$("#query").val('');
//YoutubeAPI calls go here
Template.list.search_get(url);
Links.insert({sess:Template.list.my_playlist_id,youtube_link:Session.get("search_results").items[0].snippet.title});
}
}
});
And this is what carries out the GET call:
Template.list.search_get= function(str){
var request = gapi.client.youtube.search.list({part:'snippet',q:str});
request.execute(function(response) {
str = JSON.stringify(response.result);
str = JSON.parse(str);
Session.set("search_results",str);
});
}
The top search result is displayed in a list, however the result is always displayed one result behind. Meaning the first search will yield undefined because the Session variable wouldn't have updated by that time, the second search will yield the results of the first search, and so on.
Any input on how I could go about resolving this would be great.
You don't need to use Session for this one. Try moving the Links.insert routine to your request callback, and things should start to look better ;)
I need a problem with a function to update some informations in my database (I'm using the websql). In my application, I'm showing a loading box for do the update, but, the for ends before the get function is complete and the loading box e closed.
I need that the next interaction of the for only happen when the get is finish.
Someone has a solution for this? :(
I'm using the appframework intel (jqmobi)
for (var i=0; i<len; i++){
var url = "http://172.25.129.30:8180/prax/rest/ocorrenciaJSONBean/consultarStatusOcorrencia/"+results.rows.item(i).id;
$.get(url,function(data){
tx.executeSql('UPDATE ocorrencias SET status="'+data.responseText+'" WHERE id = '+results.rows.item(i).id);
});
};
I have the following scenario:
An internal bank with a list of calls that a user opened on cell. What I want is to upgrade the status of the bank.
Today I do a query on internal bank and each id I perform a get on a webservice place to know the status of the call. I would like the next interaction is only occurred when the GET was finalized.
The problem here is that once the function ends, the screen refreshes and the message is "loading" disappears. This is occurring earlier than expected, since the end is before all GETs are finalized.
The get is not waiting to perform a call so you need to do a callback in your get function to perform the next one. You could do something like this:
var index = -1;
function perform_get(){
index++;
var url = "http://172.25.129.30:8180/prax/rest/ocorrenciaJSONBean/consultarStatusOcorrencia/"+results.rows.item(index).id;
$.get(url,function(data){
tx.executeSql('UPDATE ocorrencias SET status="'+data.responseText+'" WHERE id = '+results.rows.item(index).id);
})
.done(perform_get);
}
Now it is waiting to perform the next get call.
Hope this helps.
Think a shopping basket with some "goods" in it.
I have a <li> list of elements and each of them has a field containing a number - the amount.
Conceptually this is what i want: When the user presses a button, i loop through each <li> element picking the amount. Then i do a $.Get() to call the server with the goods id + the amount to check if the store has enough of the particular item. The server replies either True og False.
This reply is temporary stored in a html field.
Now after the looping is done, i check if there were a False reply on any of the goods.
If so i highlight the "false" items. Or else i simply submit.
OK, the problem is that my code seams to continue past my $.get() call-back function, so the final check to see if any false was returned is evaluated before the $.get() actually receives a result from the server.
Anyway this is what i think is happening...
Now lets look at some code:
var tmp = new Array();
var id = '';
var c = '';
var n = 0;
var z=0;
$('#basket').find('li.list').each(function() {
c = $(this).find('input.fldamount').val(); // this is the amount field
id = $(this).attr('id'); // this is the id no of the item
$.get('./(RPC)?OpenAgent&cmd=movewhcheckamount&unid='+id+'&count='+c, function(data) {
$('#RPCResult').val(data); // i store the returned value in a html field
if ( $('#RPCResult').val() == "true" ) {
tmp.push( id+'|'+c ); // if true is returned, i push the id & amount to an array
} else {
$(this).addClass('red'); // else i tag the item
n=n+1; // and then increment a counter
}
} ); // $('#basket')
var t = window.setTimeout( function() {
if (tmp.length > 0 && n == 0) { // if i got items in the array AND my false counter is zero
$('#SelectedArtikler').val( tmp.join(";") ); // then i store the array as text in a field
document._FlyttArtikel.submit(); // and submit
} else if (n > 0) {
// show a popup
alert("You're trying to move more items than exists...");
} else {
alert("ops, nothing to move..."); // should never end up here...
}
}, 1000);
window.clearTimeout(t);
As you can see, i have tried to counter-act the code running past my call-back function with a setTimeout, so that i basically wait a sec to give the server time to respond.
I did have another loop around the setTimeout the continued as long as my #RPCResult field was empty, but that resulted in an infinite loop.
I have the #RPCResult field visible so i can see what happens and what i see is that the popup "You're trying to move more items..." is shown and RIGTH AFTER i press ok on that popup THEN the #RPCResult field gets the result true/false.
I now some of the code can be optimized but what i'm interested in right now is getting the $.get() result in a proper fashion.
Thanks in advance ;-)
You're going to have to place the code that's going to run when all the "$.get()" calls are finished inside the callback routine. That's the only place where you can be sure that you've actually gotten the server response(s). Keep a counter of how many calls have returned, and when the counter increments up to be equal to the total number of items, then you know that all of the responses are available.
Thus:
var basketSize = $('#basket').find('li.list').length, completed = 0;
gives you the number of items and initializes the counter. Inside the callback function passed to "$.get()" you can then do something like this:
if (++completed === basketSize) {
// code currently in the "setTimeout()" callback
}
Your code already appears to be doing some of this work (the "n" variable that you increment).
Now, that said, I'll also note that it's quite crazy to perform separate HTTP transactions for each of your "items". You should bundle them all up into one HTTP request that returns a list of answers.
Also, there's really no point at all in storing the response to the "$.get()" in that "RPCresult" field; just examine the value of "data", since nothing ever looks at "RPCresult" again anyway.