Ajax abort doesn't abort on click, and duplicate request - javascript

I have a simple chat done with long polling. The chat has 2 rows of conversations (or more). Each row has a unique id and a number of messages to show.
When I click on the first line, to show the messages of this conversation, I start an ajax, and it keeps persisting inside a loop in the serverside. However, when I click on the second line, I need to end the previous ajax and start a new one.
I tried some options like the ones below, but I did not find a solution.
$('li.chat-conversation').on('click', function(){
var requestRunning = true;
var xhr;
var id = $(this).attr('data-conversation-id');
var ajaxOpt = {...};
var fn = function(){
xhr = $.ajax(ajaxOpt);
if(requestRunning){
xhr.abort();
requestRunning = false;
}
};
var interval = setInterval(fn, 2000);
});
Can anyone help me figure out a logic for this?
Note: setInterval is for example only.

Move xhr outside of click handler scope and check if it exists on click event:
var xhr = null;
$('li.chat-conversation').on('click', function() {
var id = $(this).data('conversation-id');
var ajaxOpt = {...};
// when ajax request finished (does not matter success or failed)
ajaxOpt.complete = function() {
xhr = null;
};
if(xhr) {
xhr.abort();
}
xhr = $.ajax(ajaxOpt);
});
P.S. Extend my example Yourself (:

It turns out that the server response is inserted into a div by "append". And when I click on another conversation, they are running 2
ajax at the same time. And the div is changing content, that is, in a
request, append the messages of the first conversation and in the
other request, append the messages of the second conversation.
Just check the xhr.readystate value and if it isn't 4, abort the request and clear out the conversation related to that request.
Keeping track of active requests with your own Boolean flag can easily get out of sync with multiple requests if you are not setting the flag in all the right places. But the readystate property makes it so we don't have to manage any of that.
Also, you may need to cancel your interval timer when you cancel the AJAX request.
$('li.chat-conversation').on('click', function(){
var xhr = null;
var id = $(this).attr('data-conversation-id');
var ajaxOpt = {...};
var fn = function(){
xhr = $.ajax(ajaxOpt);
// Just check the readystate and if it's not 4 (DONE),
// then cancel the current request.
if(xhr.readystate !== 4){
// Cancel the previous AJAX call(s)
xhr.abort();
// Clear out the conversation related to the request
yourConversationDiv.textContent = "";
} else if(xhr.readystate === 4 && xhr.status === 200){
// Successful result, append to existing conversation
yourConversationDiv.append(xhr.responseText);
}
};
});

Related

alert will not stop after onsubmit event - javascript ajax

The Ajax request is being sent continuously and I am not sure why
placing return false in multiple areas of the code.
//when user clicks on create channel
document.querySelector('#add-channel').onclick = () => {
// pop up modal
modal1.style.display = "block";
// Initialize new request
document.querySelector('#form1').onsubmit = () => {
const request = new XMLHttpRequest();
const chatroom = document.querySelector('#chatroom').value;
const username = localStorage.getItem('name');
request.open('POST', '/add_chatroom');
// Callback function for when request completes
request.onload = () => {
// Extract JSON data from request
const data = JSON.parse(request.responseText);
// Update the result div
if (data.success) {
// get the data that was returned.
// add it back to the list
document.querySelector('#contacts').append(li);
}
else {
alert('chatroom already exists');
}
}
// add data to send to the server
const data = new FormData();
data.append('chatroom', chatroom);
data.append('username', username);
// Send request
request.send(data);
return false;
};
};
The post request won't stop everytime I click ok even though the event should only trigger when the form with id #form1 is submitted. It triggers even when i click ok for the alert.
I ended up changing the browser from Firefox to Microsoft edge to solve the onsubmit re-running issue, why this works is beyond my knowledge.

Multiple HTTP Request to a Single File

Currently, I am attempting to call a single PHP file named 'sniffer.php'.
I am doing this async using javascript to a PHP file. The issue that I am currently having is that in the PHP code I added a sleep function to randomly (to act like a page is loading). The issue with that is two or more functions call that page it still waits until one of the pages finishes first then stops starts the other request. EG: One sleeps for 5 seconds and the other sleeps for 6 seconds. The first one completes in 5 seconds and the next one finishes at 11 seconds. What I am looking for is the one finishes in 5 seconds and the next finishes the one second after. I am not sure if it's just 'sleep' causing the issue or if the file is 'locked' because of the sleep.
Thanks for any help/feedback.
My PHP File looks like this:
$c = rand(2,10);
sleep($c);
$html .= $c;
echo json_encode(array('html'=>$html,'status'=>1));
exit;
My javascript class looks like this:
var path = '/';
var polling = {
add : function(name, obj) {
this[name] = new xAjax(obj);
return this;
}
};
function xAjax(options) {
var consti = {
};
var defaults = {
url: path + 'sniffer.php',
method: 'POST',
responseType: 'json',
async: true,
timeout: 30000,
success: function(response) {console.log(response);},
done: function() {},
beforeSend: function() {},
error: function(e) {console.log(e);},
abort: function() {}
};
var settings = Object.assign({}, defaults, options, consti);
var xhr = null;
this.run = function() {
xhr = new XMLHttpRequest();
settings.beforeSend();
xhr.responseType = settings.responseType;
xhr.open(settings.method, settings.url, settings.async);
xhr.timeout = settings.timeout;
xhr.onreadystatechange = function() {
if ( xhr.readyState == XMLHttpRequest.DONE ) {
if ( xhr.status === 200 ) {
settings.success(xhr.response);
} else {
settings.error(xhr.response);
}
settings.done();
xhr = null;
}
};
xhr.send();
return this;
};
this.abort = function() {
xhr.abort();
xhr = null;
settings.abort();
return this;
};
this.isRunning = function() {
return xhr != null;
};
this.set = function(options) {
settings = Object.assign({}, defaults, options, consti);
return this;
};
}
My creation/call to the sniffer.php:
polling.add('x');
polling.x.run();
polling.add('y');
polling.y.run();
This is happening because of sessions. It would happen with or without sleep, if the script takes time.
What happens when you start a session? PHP has to make sure the session data is current and that it will not change, it also has to make sure that the data it changes will be available in the next executions.
So if a script tries to open the session while it is open somewhere else, there's a lock, because the first script might very well change the session information still. Once the first script closes the session, the next script can get a hold of it and go on.
You can call session_write_close() to close the session for write, and thus remove the lock. While the session is closed, its value can still be accessed but it will be the value before any subsequent script changed anything (if your second script changes something before first script ends, it will not be known). Also, if you write new data to the session, it will not be saved...
From documentation
Session data is usually stored after your script terminated without
the need to call session_write_close(), but as session data is locked
to prevent concurrent writes only one script may operate on a session
at any time.
Also it seems like you are not the only one:
You can have interesting fun debugging anything with sleep() in it if
you have a session still active. For example, a page that makes an
ajax request, where the ajax request polls a server-side event (and
may not return immediately).
If the ajax function doesn't do session_write_close(), then your outer
page will appear to hang, and opening other pages in new tabs will
also stall.

asynchronous xhr request within setInterval not working | Javascript

I'm building a tracking library to send events to the backend. An event should be created every 5 seconds (configurable) and sent into the tracking queue, and the tracking queue should be sent to the backend and emptied every 5 seconds (also configurable). The expected behaviour is that an event should be sent to the backend every 5 seconds.
When I was just console.logging the events, everything was working as expected, but when I implemented the xhr request, the interval events were only created every 9 seconds or so. So an event would be sent to the backend, only once every two times the 'post' function fired.
sendData: function(){
var toSend = [].concat(Tracking.__tracking_queue);
if(toSend.length !== 0){
var sendData = this.__stringifyAndSetHeaders(toSend);
Tracking.postData(sendData);
}
},
postData: function(sendData){
var xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.responseText);
Tracking.__tracking_queue = [];
};
xhr.open("POST", sendData.url, true);
Object.keys(sendData.headers).forEach(function(key){
xhr.setRequestHeader([key], sendData.headers[key]);
});
xhr.send(sendData.body);
}
The backend is receiving the data, but not at the correct times. sendData is being called from within a setInterval loop.
setInterval(function(){
self.sendData()
}, 5000);
I had the same setup working perfectly before, in another file using axios, but I cannot use axios in this use-case.
You are resetting the tracking data at the wrong location. You read the data, than make a request, after the request is done, you delete the data. There is a period of time where data can come into the queue between the request and when it finishes.
sendData: function(){
var toSend = [].concat(Tracking.__tracking_queue);
if(toSend.length !== 0){
var sendData = this.__stringifyAndSetHeaders(toSend);
Tracking.__tracking_queue = []; //clear it here
Tracking.postData(sendData);
}
},
postData: function(sendData){
var xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log(xhr.responseText);
//Tracking.__tracking_queue = []; //don't clear it here
};
xhr.open("POST", sendData.url, true);
Object.keys(sendData.headers).forEach(function(key){
xhr.setRequestHeader([key], sendData.headers[key]);
});
xhr.send(sendData.body);
}

Normal redirect or preload

So on the net I've come across a several ways to preload / redirect a webpage.
Now's the question is this the proper way to handle a redirect with preload (Load the next page async while still showing the current page)
$.get("page.php", function (data) {
document.open();
document.write(data);
document.close();
window.history.pushState("Title", "Title", "/page.php");
$.cache = {};
}, "html");
Or should I better stay with a regular redirect?
window.location = "page.php";
The next page contains a fullscreen video and a soundtrack (audio)
Thanks.
You can use Ajax to load next page asynchronous.
Here is an example of a simple Ajax request using the GET method, written in JavaScript.
AJAX stands for Asynchronous JavaScript and XML, and for the XMLHttpRequest object to behave as AJAX, the async parameter of the open() method has to be set to true: xhr.open('get', 'send-ajax-data.php', true);
get-ajax-data.js:
// This is the client-side script.
// Initialize the Ajax request.
var xhr = new XMLHttpRequest();
xhr.open('get', 'send-ajax-data.php', true); // `true` makes the request asynchronous
// Track the state changes of the request.
xhr.onreadystatechange = function () {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
alert(xhr.responseText); // 'This is the returned text.'
} else {
alert('Error: ' + xhr.status); // An error occurred during the request.
}
}
};
// Send the request to send-ajax-data.php
xhr.send(null);
And at the end you can use below codes to reload or redirect page data:
document.getElementById("myDiv").innerHTML = xhr.responseText;

making parallel ajax requests

i have javascript code that does these things in a loop
create a div element,append it to the dom and get its reference
pass this reference to a function that makes an ajax post request
set the response of the ajax request to the innerHTML of the passed element reference
here is the code
window.onload = function () {
var categories = document.getElementById('categories').children;
for (i = 0; i < categories.length; i++) {
var link = categories[i].children[1].children[0].attributes['href'].nodeValue;
var div = document.createElement('div');
div.className = "books";
div.style.display = "none";
categories[i].appendChild(div);
getLinks(link, div);
}
}
function getLinks(url, div) {
xhr = new XMLHttpRequest();
xhr.open('POST', 'ebook_catg.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
url = encodeURIComponent(url)
var post = "url=" + url;
xhr.node=div; //in response to Marc B's suggestion
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
xhr.node.innerHTML = xhr.responseText;
xhr.node.style.display = "block";
}
}
xhr.send(post);
}
now when i check this in firebug i can see that the div element is created and appended to the categories element and its display is set to hidden. also the ajax post requests are being sent and the response is being received as expected. But the innerHTML property of div is not set and neither its display is set to block.
This means that the function getLinks loses the div reference.
when i type console.log(div) in the firefox console it says ReferenceError: div is not defined.
can somebody explain whats going on here?
in response to Franks's comment i changed readystate to readyState and i am able to attach the response of the last ajax request to the dom. so that makes it obvious that the div reference is being lost.
Thats because you are using a public (global) variable div that keeps getting overwritten.
Try this in your for loop:
for (i = 0; i < categories.length; i++) {
var link = categories[i].children[1].children[0].attributes['href'].nodeValue;
var div = document.createElement('div'); //USE var!
div.className = "books";
div.style.display = "none";
categories[i].appendChild(div);
getLinks(link, div);
}
Remember that the response handlers innards aren't "fixated" when the callback is defined, so the 'current' value of the div var doesn't get embedded into the function's definition. It'll only be resolved when the function actually executes, by which time it might have been set to some completely other div, or been reset to null as the parent function's scope has been destroyed.
You could store the div value as a data attribute on the xhr object, which you can then retrieve from within the callback:
xhr.data('thediv', div);
xhr.onreadystatechange = function () {
if (xhr.readystate == 4) {
div = xhr.data('thediv');
etc....
Ok, you've got a few globals going on that you don't want. Rule of thumb: unless you need to access a variable outside of a function, place var in front of it. Otherwise you'll have data clobbering itself all over the place:
// changed the name to `d` because div seems to already be a global var.
function getLinks(url, d) {
// make xhr a local variable so it won't get re-written.
var request = new XMLHttpRequest();
request.open('POST', 'ebook_catg.php', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
url = encodeURIComponent(url)
var post = "url=" + url;
request.onreadystatechange = function () {
// when the request was global, this would be false until the last
// request completed
if (request.readyState == 4) {
// since d only exists as a parameter to getLinks, this should
// already be bound when the onreadystatechange is created.
d.innerHTML = request.responseText;
d.style.display = "block";
}
}
request.send(post);
}
So, why did I just do such strange, strange things? Well, it looks like div was being assigned as a global variable and while JS should always look to function parameter name for binding, we want to eliminate all possible problems. So I changed the name of that variable. Then I set xhr to reflect a local variable with the var keyword. I also changed the name to request. Once again, it shouldn't matter -- var means that the variable will be bound to that scope, but the change is harmless and since I don't know what else you have, I decided to remove ambiguities. If it does not help JS, it will at least help the reader.
NOTE:
The important part of the above answer is var in front of request.
here i am answering my question.The following code works,i mean the response from each post is appended to the corresponding div element.
var xhr=new Array();
window.onload=function() {
var categories=document.getElementById('categories').children;
for(i=0;i<categories.length;i++)
{
var link=categories[i].children[1].children[0].attributes['href'].nodeValue;
var div=document.createElement('div');
div.className="books";
div.style.display="none";
categories[i].appendChild(div);
getLinks(link,div,i);
}
}
function getLinks(url,div,i)
{
xhr[i]=new XMLHttpRequest();
xhr[i].open('POST','ebook_catg.php',true);
xhr[i].setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
url=encodeURIComponent(url)
var post="url="+url;
xhr[i].node=div;
xhr[i].onreadystatechange=function() {
if(xhr[i].readyState==4)
{
xhr[i].node.innerHTML=xhr[i].responseText;
xhr[i].node.style.display="block";
}
}
xhr[i].send(post);
}
i am not marking it as accepted because i still dont understand why i need to use an array of xhr since a local xhr object should be enough because each time the onreadystate function executes it has the reference of the xhr object. Now since javascript functions are also objects therefore every instance of onreadystate function should have its own reference of xhr object and therefore i shouldnt need to create an array of xhrs.
please correct me if i am wrong here

Categories

Resources