Ajax too slow - Recursion - javascript

A ajax code that i am using to request a page is consuming too much memory and is making the browser slow and everything lag. It seems like there is recursion going on and i dont know of any way to prevent it. Here is what the code looks like.
$(".item").each(function() {
$this = $(this);
var dataString = {s: "<?echo $_SESSION['currentview_'.$stamp]?>", r:"<?echo $search_usernumber?>", st: "<?echo $stamp?>"};
$.ajaxSetup({cache:false});
function timeLeft() {
$.ajax({
type: "POST",
url: "get_content_home.php",
dataType: "html",
data: dataString,
success: function(result) {
$this.html(result);
//console.log("a");
window.setInterval(function() {
timeLeft();
}, 500);
}
});
}
timeLeft();
});
How can i solve this problem? Thanks in advance.

You are recursing and you shouldn't be using this form of nested setInterval. Doing this, will cause an explosion of interval instances. Instead of using setInterval, schedule additional requests using setTimeout.
setInterval will fire and continue firing every interval until you tell it to stop.
setTimeout will fire once.
Let's consider the following code which should address some of the issues you are having in this question as well as your other 2 questions.
First off, as we said before, don't use setInterval unless you actually want it to run forever. Additionally, don't nest the setInterval creations unless you actually mean to.
Instead, let's create a recursive function getTimeLeft() that will handle firing the request and scheduling the next check for time left after some duration.
This example also mocks the $.ajax() function so that you can see the function in action since we don't have an actual back end to use.
// Fake server side data to keep track of time lefts
const timeLefts = {
foo: 0,
bar: 0,
fizz: 0,
buzz: 0
};
const timeLeftsUpdateInterval = setInterval(() => {
for (const [key, val] of Object.entries(timeLefts)) {
timeLefts[key] = Math.min(val + Math.random() * 10, 100);
}
if (Object.entries(timeLefts).every(([k, v]) => v >= 100)) {
clearInterval(timeLeftsUpdateInterval);
}
}, 1000);
// Mock $.ajax function to stub sending AJAX requests
function $ajax(kwargs) {
return {
done: cb => {
setTimeout(() => {
cb(timeLefts[kwargs.data.x]);
}, 500);
}
};
}
// We will check for an update every second after the last request finishes
const timeLeftCheckInterval = 1000;
// Continuously check to see how much time is left for an element
function getTimeLeft(el) {
// Make our request data
const dataString = {
s: "<?echo $_SESSION['currentview_'.$stamp]?>",
r: "<?echo $search_usernumber?>",
st: "<?echo $stamp?>",
// My custom property to make this work
x: el.dataset.item
};
// Make our request to get the time left
const req = $ajax({ // Using our mock $.ajax
type: "POST",
url: "get_content_home.php",
dataType: "html",
data: dataString
});
// Once the request has finished
req.done(data => {
// Set the time left to the element
el.innerHTML = data;
// Have some condition so that you don't check for time left forever
// Eventually there will be no time left right? Why keep checking?
if (data.timeleft <= 0) return;
// Schedule another round of checking for time left after some duration
setTimeout(() => {
getTimeLeft(el);
}, timeLeftCheckInterval);
});
}
// Kick off getting timeleft for all .items
Array.from(document.querySelectorAll(".item"))
.forEach(el => getTimeLeft(el));
<ul>
<li class="item" data-item="foo"></li>
<li class="item" data-item="bar"></li>
<li class="item" data-item="fizz"></li>
<li class="item" data-item="buzz"></li>
</ul>
This code will address the issue that you are having in 2 Ajax non-blocking because each element will have it's own logic of going and fetching time left and updating itself.
This also addresses the issue that you are potentially facing in Timer in Ajax - Preemption because now the element won't check to see how much time is left again until after the previous check is finished.

Related

Progress Bar on Promise.all array of $.ajax() calls

FINAL UPDATE 25/06/20: Okay, so there were multiple reasons this was not working.
$.ajax() is a pain to work with as it doesn't really return proper promises. So any implementations relying on that didn't work. Solution to this part was to change over to using AXIOS for the calls.
I'm actually making a lot of calls during this process. I initially thought that the first set of calls I was making was small, and didn't cause a lot of the loading time before the bar fills up. I was expecting the second set of calls to take a long time, and thus only coded the progress bar to take that into consideration. After analysing the network traffic, it turns out it was the complete reverse. So my solution was to actually have the progress bar fill up twice: once for the "preparation call" (which actually take the longest), and then fills up again on response of the actual request calls.
Thanks to all that gave their advice, it helped once I figured out where I was going wrong!
UPDATE 25/06/20: Here is a link to the Gist with the entire function as it is too big to post here: Function in Gist. Line 215 is where I add the $.ajax() calls to matchPromises. Line 274 is where I call Promise.allSettled(matchPromises)
I am having a lot of issues trying to get a working progress bar for this. I'm not going to post my actual code as it's way too long, but I'll post the gist of it.
The ajax calls are something like this:
$.ajax({
"url": myUrl,
"method": "POST",
"success": (data) => {
doStuff
// POINT A
},
"error": (data) => {
doStuff
}
})
I push them into an array called matchPromises and then I call Promise.allSettled(matchPromises) on that. This all works fine.
The problem I'm having is trying to update a progress bar to indicate how many promises are done. I have the following at the top of the function where I am doing everything else:
let currentProgress = 0;
let maxProgress = 0;
function displayProgress() {
currentProgress++;
let calculatedWidth = (currentProgress / maxProgress) * 100;
$('#progressBar').width(`${calculatedWidth}%`);
$('#progressBar').attr('aria-valuenow', currentProgress);
}
I update the maxProgress right before I call Promise.allSettled(matchPromises) by using matchPromises.length.
I have tried placing displayProgress() in the success part of the $.ajax calls - but the issue with that is that maxProgress will always stay at 0 every time it's called.
I've tried a ton of various methods of promisifying the $.ajax by wrapping it in a new Promise and adding .then() - and this correctly reads maxProgress - BUT no matter which method I've tried, it only calls this in a huge block AFTER the Promise.allSettled has finished.
I've been at this for hours and have tried so many different methods. Really hope someone out there can help me with this because I'm at the end of my tether.
Since there is some code lacking to apply that to your situation, I tried to sketch a helpful solution anyway.
I hope this solves your issue or at least gives you some inspiration.
const getLongRunningPromise = (milliseconds, value) => {
return new Promise((resolve) => {
setTimeout(() => resolve(value), milliseconds)
});
};
const promises = [getLongRunningPromise(5000, 'a'), getLongRunningPromise(1000, 'b')];
let doneCount = 0;
const overallCount = promises.length;
const handleProgress = (result) => {
doneCount++;
const percentageDone = doneCount/overallCount*100;
console.log(`${percentageDone}% Done`);
return result;
};
Promise.all(promises.map(p => p.then(handleProgress))).then((results) => console.log('results', results));
Assuming you have a function foo(), you can write something like this:
function foo() {
let currentProgress = 0;
let someArray = .......;
let matchPromises = someArray.map(function(myUrl) {
return $.ajax({
'url': myUrl,
'method': 'POST',
'success': (data) => {
// doStuff
},
'error': (jqXHR, textStatus, errorThrown) => {
// doStuff
}
})
.always(function() {
displayProgress(++currentProgress, matchPromises.length);
});
});
Promise.allSettled(matchPromises).then(...); // what happens here has nothing to do with displaying progress.
}
function displayProgress(current, max) {
let calculatedWidth = (current / max) * 100;
$('#progressBar').width(`${calculatedWidth}%`).attr('aria-valuenow', current);
}
Thus, currentProgress is an inner member of foo() and maxProgress never needs to be assigned - it's just matchPromises.length.
If it's not called elsewhere, displayProgress() could also be an inner member of foo(), or just move the two lines of code inside the .always() callback (and adapt accordingly).

Globally Rate Limit $.ajax requests in jquery

Is there a way via $.ajaxSetup to throttle requests so anything after X requests in Y seconds don't happen until time has passed so throttling can reset itself? I know I could use jquery throttle,
But I am looking for a global way to effect all $.ajax, $.get, and $.post.
For example I might setup that I will only allow 20 requests in 5 seconds. On the 21st request I would have an error handler to display a message. Every 5 seconds the counter would start over so they can make requests again.
Here is a way to rateLimit all ajaxRequests. You can define a rate limit interval in MS and how many request are allowed in a given time period.
var RATE_LIMIT_IN_MS = 100;
var NUMBER_OF_REQUESTS_ALLOWED = 2;
var NUMBER_OF_REQUESTS = 0;
setInterval(function()
{
NUMBER_OF_REQUESTS = 0;
}, RATE_LIMIT_IN_MS);
$.ajaxSetup ({
beforeSend: function canSendAjaxRequest()
{
var can_send = NUMBER_OF_REQUESTS < NUMBER_OF_REQUESTS_ALLOWED;
NUMBER_OF_REQUESTS++;
return can_send;
}
});
Couldn't you put the ajax call into a function and track how often that function is called?
var globalCount = 0;
var ajaxOperations = {
init: function(){
//init
},
loadNewsletterTemplate: function(templateDataFile){
globalCount++;
$.ajax({
url: "data/" + templateDataFile,
type: "GET",
success: function(response){
globalCount--;
formProcessing.convertToHTML_newsLetter_weeklyUpdates(response);
//console.log("response" + response);
}
});
}
}
Every time ajaxOperations is called you could iterate a global number and every time a success is performed you could do a retraction, thereby maintaining a steady flow. If (globalCount < 5) {call new ajax event}
Throttle one function, isSafe(), that returns true if it's good to go or false otherwise.
In ajaxSetup, have beforeSend() return the result of isSafe(). If it is false, the request will not be sent.
I haven't tested it, but it would be something like:
const isSafe = $.throttle(numberOfMillis, () => true);
$.ajaxSetup({
beforeSend: isSafe
});

Clear a Javascript Interval

I am running a HTTP Request to a file and depending on the response whether it be "200" or another response a success or error function is ran. This request takes place every second.
The problem I am facing is when I get lots of error responses they all run together and the last one doesn't stop e.g. End the interval to start a new one.
The red light begins to flash way too fast. Can anyone help me out. My code is below and I have been playing with it for a few hours now but can't seem to get to the bottom of it.
var requestResponses = {
greenLight: $('.cp_trafficLight_Light--greenDimmed'),
redLight: $('.cp_trafficLight_Light--redDimmed'),
greenBright: 'cp_trafficLight_Light--greenBright',
redBright: 'cp_trafficLight_Light--redBright',
init: function (url) {
setInterval(function () {
requestResponses.getResponse(url);
}, 1000);
},
successResponse: function () {
var redBright = requestResponses.redBright,
greenBright = requestResponses.greenBright;
requestResponses.errorCode = false;
requestResponses.redLight.removeClass(redBright);
requestResponses.greenLight.addClass(greenBright);
},
errorResponse: function () {
requestResponses.runOnInterval();
},
runOnInterval: function () {
// clearInterval(runInterval);
var redBright = requestResponses.redBright,
greenBright = requestResponses.greenBright,
redLight = requestResponses.redLight;
requestResponses.greenLight.removeClass(greenBright);
var runInterval = setInterval(function () {
if (requestResponses.errorCode === true) {
redLight.toggleClass(redBright);
}
}, 400);
},
getResponse: function (serverURL) {
$.ajax(serverURL, {
success: function () {
requestResponses.errorCode = false;
requestResponses.successResponse();
},
error: function () {
requestResponses.errorCode = true;
requestResponses.errorResponse();
},
});
},
errorCode: false
}
requestResponses.init('/status');
Appreciate the help.
Javascript is an event driven language. Do not loop inifinitely to check things periodically. There are places to do so but most of the time either calling a delay function (setTimeout) repeatedly when needed or using a callback would be better method.
Using setInterval with request, think what happens if requests start taking longer than your interval.
In your case, you have two loops created with setInterval. First one is the request which will run every 1 sec. Instead of using setInterval, you can modify your code to run setTimeout only after a request finishes and do other tasks just before re-running the next request :
function runRequest(...) {
$.ajax(serverURL, {
...
complete: function () {
setTimeout(runRequest, 1000);
}
...
});
}
function lightsOnOff() {
var redBright = requestResponses.redBright,
greenBright = requestResponses.greenBright,
redLight = requestResponses.redLight;
requestResponses.greenLight.removeClass(greenBright);
if (requestResponses.errorCode === true) {
redLight.toggleClass(redBright);
}
}
setInterval(lightsOnOff, 400);
The setInterval() method repeats itself over and over, not just one time. Your error response handler is then invoking the routine that creates another setInterval(), and so on. Until you have so many processes running that you get the flashing red light issue.
The solution is to only invoke the logic where the setInterval() call is made once. Or, even better, use setTimeout() to call the routine. It is run one-time and likely better for your use.

Why aren't ajax calls being made with a specified delay

I am trying to call an action on a controller with ajax: 10 times with a 2 second delay in my MVC5 application.
Here is the code I've written:
$(document).ready(function () {
(function loop(i) {
setTimeout(function() {
var d = new Date();
console.log(d.getTime());
callAjax();
console.log("works " + i);
if (--i) loop(i);
},
2000); // ms
})(10);
function callAjax() {
$.ajax({
url: '/Home/StartElection',
type: 'POST',
data: "test",
async: true
})
.done(function (partialViewResult) {
$("#partialTable").html(partialViewResult);
});
};
});
The console log is as expected (with a 2 second delay) but the calls to the controller happen instantly - when I set a break in Visual Studio on the controller action the next call after continuing takes 2ms
I can't see why this is happening - can any one help?
Edit: I added a console log of Date.getTime() just before the ajax call & there are 2000 ms between each
You have to change this line alone async: true -> async: false
because the AJAX calls will be made asynchronous if this property is set to true.
And so your ajax calls have no time delay.
Hope this helps.
As far as your client side code is concerned it seems to be working well as far as calls are concerned. With that said here are a few things to consider when dealing with ajax:
1) You have control over the number of times that you can call a remote service but you have no control over the time it will take for that service to respond.
2) As such it is usual good practise for most cases to not make ajax calls in a loop (this somewhat defeats the general purpose of ajax). Rather use the response to each call to then make the next call (but then of course we would need to know exactly what it is you are trying to build to suggest an exact solution).
So the closest thing to what you are looking for using ajax I think would be more of something like this:
$(document).ready(function () {
/*(function loop(i) {
setTimeout(function() {
var d = new Date();
console.log(d.getTime());
callAjax();
console.log("works " + i);
if (--i) loop(i);
},
2000); // ms
})(10);*/
var i=0;
function callAjax() {
var requestTimeMessage = "fetch #"+i+" at: "+new Date().getTime();
console.log(requestMessage);
$.ajax({
url: '/Home/StartElection',
type: 'POST',
data: "test",
async: true
})
.done(function (partialViewResult) {
var requestTimeMessage = "response #"+i+" at: "+new Date().getTime();
console.log(requestMessage);
$("#partialTable").html(partialViewResult);
i++;
if(i<10) {
/*Note this assumes a quick response for each call.
* Otherwise the response together with this delay will total a wait longer than 2 seconds.
*/
setTimeout(function() {
callAjax();
2000);
}
});
};
});
But as I said. I would need to know exactly what you are trying to achieve to give more appropriate answer to your question.

jQuery AJAX polling for JSON response, handling based on AJAX result or JSON content

I'm a novice-to-intermediate JavaScript/jQuery programmer, so concrete/executable examples would be very much appreciated.
My project requires using AJAX to poll a URL that returns JSON containing either content to be added to the DOM, or a message { "status" : "pending" } that indicates that the backend is still working on generating a JSON response with the content. The idea is that the first request to the URL triggers the backend to start building a JSON response (which is then cached), and subsequent calls check to see if this JSON is ready (in which case it's provided).
In my script, I need to poll this URL at 15-second intervals up to 1:30 mins., and do the following:
If the AJAX request results in an error, terminate the script.
If the AJAX request results in success, and the JSON content contains { "status" : "pending" }, continue polling.
If the AJAX request results in success, and the JSON content contains usable content (i.e. any valid response other than { "status" : "pending" }), then display that content, stop polling and terminate the script.
I've tried a few approaches with limited success, but I get the sense that they're all messier than they need to be. Here's a skeletal function I've used with success to make a single AJAX request at a time, which does its job if I get usable content from the JSON response:
// make the AJAX request
function ajax_request() {
$.ajax({
url: JSON_URL, // JSON_URL is a global variable
dataType: 'json',
error: function(xhr_data) {
// terminate the script
},
success: function(xhr_data) {
if (xhr_data.status == 'pending') {
// continue polling
} else {
success(xhr_data);
}
},
contentType: 'application/json'
});
}
However, this function currently does nothing unless it receives a valid JSON response containing usable content.
I'm not sure what to do on the lines that are just comments. I suspect that another function should handle the polling, and call ajax_request() as needed, but I don't know the most elegant way for ajax_request() to communicate its results back to the polling function so that it can respond appropriately.
Any help is very much appreciated! Please let me know if I can provide any more information. Thanks!
You could use a simple timeout to recursively call ajax_request.
success: function(xhr_data) {
console.log(xhr_data);
if (xhr_data.status == 'pending') {
setTimeout(function() { ajax_request(); }, 15000); // wait 15 seconds than call ajax request again
} else {
success(xhr_data);
}
}
Stick a counter check around that line and you've got a max number of polls.
if (xhr_data.status == 'pending') {
if (cnt < 6) {
cnt++;
setTimeout(function() { ajax_request(); }, 15000); // wait 15 seconds than call ajax request again
}
}
You don't need to do anything in your error function unless you want to put an alert up or something. the simple fact that it error will prevent the success function from being called and possibly triggering another poll.
thank you very much for the function. It is a little bit buggy, but here is the fix. roosteronacid's answer doesn't stop after reaching the 100%, because there is wrong usage of the clearInterval function.
Here is a working function:
$(function ()
{
var statusElement = $("#status");
// this function will run each 1000 ms until stopped with clearInterval()
var i = setInterval(function ()
{
$.ajax(
{
success: function (json)
{
// progress from 1-100
statusElement.text(json.progress + "%");
// when the worker process is done (reached 100%), stop execution
if (json.progress == 100) clearInterval(i);
},
error: function ()
{
// on error, stop execution
clearInterval(i);
}
});
}, 1000);
});
The clearInterval() function is becomming the interval id as parameter and then everything is fine ;-)
Cheers
Nik
Off the top of my head:
$(function ()
{
// reference cache to speed up the process of querying for the status element
var statusElement = $("#status");
// this function will run each 1000 ms until stopped with clearInterval()
var i = setInterval(function ()
{
$.ajax(
{
success: function (json)
{
// progress from 1-100
statusElement.text(json.progress + "%");
// when the worker process is done (reached 100%), stop execution
if (json.progress == 100) i.clearInterval();
},
error: function ()
{
// on error, stop execution
i.clearInterval();
}
});
}, 1000);
});
You can use javascript setInterval function to load the contents each and every 5 sec.
var auto= $('#content'), refreshed_content;
refreshed_content = setInterval(function(){
auto.fadeOut('slow').load("result.php).fadeIn("slow");},
3000);
For your reference-
Auto refresh div content every 3 sec

Categories

Resources