Use .abort() to cancel existing ajax request if user sends another - javascript

I am attempting to follow the advice within this question to fit the following scenario:
On keyup, send an AJAX request to the server with the term in the input field. But if they continue to type, abort any existing AJAX requests so only one request is sent.
Here is my code as it stands:
jQuery(document).ready(function($) {
var $input = $('#s'),
inputVal = '',
inputCount = '';
function process_asr_request() {
$.ajax({
url: MyAjax.ajaxurl,
data: {
'action':'pondera_asr_request',
'inputVal' : inputVal
},
success:function(data) {
$('#search-results').append( data )
},
error: function(errorThrown){
console.log( errorThrown);
}
});
}
$input.on("keyup", function() {
// record the value of the input
inputVal = $input.val(),
// check the lenght on the value
inputCount = inputVal.length,
// define array for ajax requests
requests = [];
// Only process once there are 3 characters
if (inputCount > 2) {
// push ajax requests into the array
requests.push(
process_asr_request(i)
);
// loop through the array of requests
for(
var i = 0;
i < requests.length;
i++
)
// kill any queued requests
requests[i].abort();
};
});
});
I have two questions:
Is this approach valid for what I am looking to achieve
I get an "Uncaught TypeError: Cannot call method 'abort' of undefined" error with the above code. Where am I going wrong.
I'm fairly new to AJAX so please pardon my naivety.

Close, just need to return the xhr so you can abort it:
function process_asr_request(inputVal) {
return $.ajax({
url: MyAjax.ajaxurl,
data: {
'action':'pondera_asr_request',
'inputVal' : inputVal
},
success:function(data) {
$('#search-results').append( data )
},
error: function(errorThrown){
console.log( errorThrown);
}
});
}
Also this loop would be better written as below, so old xhrs are removed from requests:
var xhr;
while (xhr = requests.pop()) {
// kill any queued requests
xhr.abort();
}
requests.push(process_asr_request(inputVal));
Another note, requests should be outside the event loop if you want this to work and you have several globals in this function.
var requests = [];
$input.on("keyup", function() {
var inputVal = $input.val(),
// check the lenght on the value
inputCount = inputVal.length;
//...
});

var currXHR;
function process_asr_request() {
if(currXHR && currXHR.abort) currXHR.abort();
currXHR = $.ajax({
url: MyAjax.ajaxurl,
data: {
'action':'pondera_asr_request',
'inputVal' : inputVal
},
success:function(data) {
$('#search-results').append( data )
},
error: function(errorThrown){
console.log( errorThrown);
}
});
}

Related

How to Execute ajax call after the scroller reaches the bottom

I'm correcting working on lazy loading for 200 array of objects and APIs are provided to me to extract JSON from the server (by passing index, row count as parameter for the get AJAX and on response I get the data and the Boolean of whether there are more rows or not). But the problem is that initially I was able to get the data of 10 from the 200 but while I set the scroll function on the div it displays duplicate data which are already appended on the div. Stucked in this problem for a day.
Hope you guys shed some light on me.
var listgen = (function() {
var mc = {};
mc.startindex = 1;
mc.rowcount = 10;
mc.hasmorerows = false;
mc.entity = "requests"
//Declared variables:
mc.initComponent = function() {
var entity = "requests";
mc.callAjaxForList(mc.entity, mc.startindex, mc.rowcount);
$("#reqbody").on('scroll', function() {
if (mc.hasmorerows && ($(this)[0].scrollHeight <= $(this).scrollTop() + $(this).innerHeight())) {
console.log('reached')
mc.callAjaxForList(mc.entity, mc.startindex, mc.rowcount);
}
console.log("scroll");
})
}
mc.callAjaxForList = function(entity, startindex, rowcount) {
var options = {
"list_info": {
"row_count": rowcount,
"start_index": startindex
}
}
$("#reqbody").addClass("loading");
$.ajax({
url: "/data/" + entity,
data: {
"input_data": JSON.stringify(options)
},
contentType: "application/json; charset=utf8",
type: "GET",
success: function(json) {
mc.hasmorerows = json.list_info.has_more_rows
mc.onDataLoading(json);
},
});
}
mc.onDataLoading = function(json) {
//this is where i append the data from the json
mc.startindex += mc.rowcount
}
return mc;
})()
listgen.initComponent();
Scroll is a very high-frequent event, so I think that you have several ajax calls with same data before actually your onDataLoading called, and range incremented. So I whould add mutex.
// ...
mc.loaging = false; // mutex
$("#reqbody").on('scroll', function(){
if(mc.hasmorerows && ($(this)[0].scrollHeight<=$(this).scrollTop()+$(this).innerHeight())){
console.log('reached')
if (!mc.loading) // add check here
mc.callAjaxForList(mc.entity,mc.startindex,mc.rowcount);
}
console.log("scroll");
})
}
mc.callAjaxForList= function(entity,startindex,rowcount){
// ...
mc.loading = true;
$.ajax({
// ...
success:function(json){
mc.hasmorerows=json.list_info.has_more_rows
mc.onDataLoading(json) ;
mc.loading = false;
},
error: ()=> mc.loading = false
});
}
So our mc.loading will tell us if ajax already completed (do not forget to reset it's value on ajax error)

Javascript object counter not incrementing as expected

I am having trouble getting the errorCount property to increase during code execution. The problem I am having is occurring inside of the $.ajax request, more specifically the addError() method.
If I use the following code below to check the current count of errorCount it always returns 0 even though I have manually created an error to occur. But inside of the ajax method after I call addError() and then check the value of errorCount it shows 1 like it should. What did I do wrong?
var boom = new test(settings, formData, search);
console.log(boom.errorCount);
boom.queueCalls(settings);
console.log(boom);
console.log(boom.errorCount);
Here is the object code:
function test(a, b, c) {
this.settings = a;
this.formData = b;
this.search = c;
this.errorCount = 0;
}
test.prototype = {
constructor: test,
queueEmails:function(settings, formData, search) {
var url = '/example-url-endpoint';
var data = {postData: settings + "&" + formData + "&" + search};
this.sendRequest(url, data);
},
queueCalls:function(settings) {
var url = '/example-url-endpoint2';
this.sendRequest(url, settings);
},
addMessage:function(response) {
flashMessage(response.Message, response.Result);
},
addError:function() {
this.errorCount++;
},
sendRequest:function(url, data) {
var blah = this;
j$.ajax({
type: "POST",
url: url,
data: data,
dataType: 'json',
success: function(data) {
response = JSON.parse(data);
if(response.Result != 'error') {
blah.addMessage(response);
} else {
blah.addMessage(response);
blah.addError();
console.log(blah.errorCount);
}
},
error: function(e, textStatus, errorThrown) {
blah.addError();
console.log(blah.errorCount);
alert("There was an error creating the queue");
}
});
}
}
The problem is you are doing an asynchronous (AJAX) call. When you call the queueCalls function, it makes the AJAX call, then runs your console.log statements. It does not wait until the AJAX call is done, and you have received your errors to run the console statements. If you want to do that, look at the jQuery documentation for .ajax(), and either make your AJAX call not asynchronous, or put your console statements in a .done() event handler that will fire after the AJAX call is complete.

Ajax calls inside loop need sequential responses

I need to make 3 or less ajax calls, and the responses need to be appended to the dom in the same order they were requested.
I have the following function, but the problem is that the responses that I get are not necessarily in the correct order when they get appended to the dom.
I wouldn't want to use the async: false property because it blocks the UI and it's a performance hit of course.
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
});
}
};
You need to append the article to a certain position, based on for example the i variable you have. Or you could wait for all of the requests and then append them in order. Something like this:
mod.getArticles = function( ){
var load = function( id ) {
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
});
};
var onDone = function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
};
var requests = [];
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
requests.push(load(mod.vars.pushIds[i]));
}
$.when.apply(this, requests).done(function() {
var results = requests.length > 1 ? arguments : [arguments];
for( var i = 0; i < results.length; i++ ){
onDone(results[i][0]);
}
});
};
Here is an example using i to append them in the proper order when they all finish loading:
mod.getArticles = function( ){
// initialize an empty array of proper size
var articles = Array(3 - mod.vars.ajaxCount);
var completed = 0;
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
// prevent i from being 3 inside of done callback
(function (i){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
completed++;
if (data.length) {
// store to array in proper index
articles[i - mod.vars.ajaxCount] = data;
} else {
console.error('get article ajax output error');
}
// if all are completed, push in proper order
if (completed == 3 - mod.vars.ajaxCount) {
// iterate through articles
for (var j = mod.vars.ajaxCount; j < 3; j++) {
// check if article loaded properly
if (articles[j - mod.vars.ajaxCount]) {
mod.appendArticle(articles[j - mod.vars.ajaxCount]);
}
}
}
});
}(i));
}
};
var success1 = $.ajax...
var success2 = $.ajax...
var success3 = $.ajax...
$.when(success1, success2, success3).apply(ans1, ans2, ans3) {
finalDOM = ans1[0]+ans2[0]+ans3[0];
}
Check this for more reference. This is still async, but it waits for all of them to complete. You know the order of invocation already, as its done through your code, so add the dom elements accordingly.
Solutions that rely solely on closures will work up to a point. They will consistently append the articles of a single mod.getArticles() call in the correct order. But consider a second call before the first is fully satisfied. Due to asynchronism of the process, the second call's set of articles could conceivably be appended before the first.
A better solution would guarantee that even a rapid fire sequence of mod.getArticles() calls would :
append each call's articles in the right order
append all sets of articles in the right order
One approach to this is, for each article :
synchronously append a container (a div) to the DOM and keep a reference to it
asynchronously populate the container with content when it arrives.
To achieve this, you will need to modify mod.appendArticle() to accept a second parameter - a reference to a container element.
mod.appendArticle = function(data, $container) {
...
};
For convenience, you may also choose to create a new method, mod.appendArticleContainer(), which creates a div, appends it to the DOM and returns a reference to it.
mod.appendArticleContainer = function() {
//put a container somewhere in the DOM, and return a reference to it.
return $("<div/>").appendTo("wherever");
};
Now, mod.getArticles() is still very simple :
mod.getArticles = function() {
//Here, .slice() returns a new array containing the required portion of `mod.vars.pushIds`.
//This allows `$.map()` to be used instead of a more cumbersome `for` loop.
var promises = $.map(mod.vars.pushIds.slice(mod.vars.ajaxCount, 3), function(id) {
var $container = mod.appendArticleContainer();//<<< synchronous creation of a container
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML'
}).then(function(data) {
if (data.length) {
mod.appendArticle(data, $container);//<<< asynchronous insertion of content
} else {
return $.Deferred().reject(new Error("get article ajax output error"));
}
}).then(null, function(e) {
$container.remove();//container will never be filled, so can be removed.
console.error(e);
return $.when(); // mark promise as "handled"
});
});
return $.when.apply(null, promises);
};
mod.getArticles() now returns a promise of completion to its caller, allowing further chaining if necessary.
Try utilizing items within mod.vars array as indexes; to set as id property of $.ajaxSettings , set returned data at this.id index within an array of responses. results array should be in same order as mod.vars values when all requests completed.
var mod = {
"vars": [0, 1, 2]
};
mod.getArticles = function () {
var results = [];
var ids = this.vars;
var request = function request(id) {
return $.ajax({
type: "GET",
url: "/ajax/article/" + id + "/",
// set `id` at `$.ajaxSettings` ,
// available at returned `jqxhr` object
id: id
})
.then(function (data, textStatus, jqxhr) {
// insert response `data` at `id` index within `results` array
console.log(data); // `data` returned unordered
// set `data` at `id` index within `results
results[this.id] = data;
return results[this.id]
}, function (jqxhr, textStatus, errorThrown) {
console.log("get article ajax error", errorThrown);
return jqxhr
});
};
return $.when.apply(this, $.map(ids, function (id) {
return request(id)
}))
.then(function () {
$.map(arguments, function (value, key) {
if (value.length) {
// append `value`:`data` returned by `$.ajax()`,
// in order set by `mod.vars` items:`id` item at `request`
mod.appendArticle(value);
} else {
console.error("get article ajax output error");
};
})
});
};
mod.getArticles();
jsfiddle http://jsfiddle.net/6j7vempp/2/
Instead of using a for loop. Call your function in response part of previous function.
//create a global variable
var counter = 0;
function yourFunc(){
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[counter ];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
//increment & check your loop condition here, so that your responses will be appended in same order
counter++;
if (counter < 3)
{ yourFunc(); }
});
};
}
I'm faced same problem i'm solve this problem using following way.
just use async for get sequence wise response
<script type="text/javascript">
var ajax1 = $.ajax({
async: false,
url: 'url',
type: 'POST',
data: {'Data'},
})
.done(function(response) {
console.log(response);
});

Get latest ajax request and abort others

I have been searching and this problem seems simple but cannot find answer. I have multiple request calling different url. But for each url, I only want the result once and it must be the last one in the same url being called. My issue now is "how to get the last one only?" I looked at this and it seems to be 3 years old:
http://plugins.jquery.com/project/ajaxqueue
Any other way to do this nicely and cleanly? If there is something like this, it would be perfect:
queue: "getuserprofile",
cancelExisting: true
(where the existing ajax in getuserprofile queue will be canceled)
Thanks
This explains how to use jQuery to do an ajax call and how to abort it. All you need to do is create an array that stores each request. You could then cancel the previous request while adding the new one.
ajaxRequests = new Array();
queueRequest = function() {
if(ajaxRequests[ajaxRequests.length - 1]) {
ajaxRequests[ajaxRequests.length - 1].abort();
}
ajaxRequests[ajaxRequests.length] = //Insert New jQuery AJAX call here.
}
Since we only want the result of the last request, this is very simple and works.
var ajax = null;
var getFoo = function() {
if(ajax) ajax.abort();
ajax= $.ajax({});
};
getFool();
getFool();
getFool();
getFool();
getFool();
getFool();
only the last request is executed.
Instead of using library, you can use Basic jquery Ajax method :
beforeSend:{}
For example:
xhr = jQuery.ajax({
url: /*Your URL*/
type: "POST",
data: {
//data
},
/* if there is a previous ajax request, then we abort it and then set xhr to null */
beforeSend : function() {
if(xhr != null) {
xhr.abort();
}
},
success:function(){}
});
Yes it's posibble...
In general, I do not recommend stopping requests on the server side, there may be some problems that are hard to detect later.
The idea is to just check the status of a sent request and if necessary, just not send another one.
Here is an example
let activeRequest = false; //global var
let request = null;
$(document).ready(function(){
$('#user').keyup(function(){
let query = $(this).val();
if (query != '') {
const xhr = new XMLHttpRequest();
if (activeRequest === false){
activeRequest = true;
let request = $.ajax({
url: 'https://baconipsum.com/api/?type=all-meat',
method: "POST",
beforeSend: function(){
//Some code here
},
data: {
"type": $(":radio:checked").val(),
"tags": query,
},
success: function (data) {
//Code if succes
console.log('Request with val: ' + query);
},
complete: function() {
activeRequest = false;
}
});
if(!request){
console.log('Req does exist');
}
request.done(function( ) {
activeRequest = false;
console.log('Done, Request Finish');
request = false;
});
}else{
//If ajax still request abort it
console.log('Exiting Request - LastOne Still In que')
//request.abort();
activeRequest = true;
}
} //End iF query != ''
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" name="user" id="user" style="width: 200px;" placeholder="Enter User Name" />
It takes some work to customize it for you but I think it will help guide everyone for you.
//Edit
I forgot, u can customize this code, for example add
let value = $('#output').val();
value.slice(0,-1);
If the request is still pending... Or just another idea, just block the possibility of entering more characters, there are plenty of solutions

Using jQuery, how can I store the result of a call to the $.ajax function, to be re-used?

Thanks for reading this.
I imagine this is really a javascript question, and my title probably does not get at the heart of what I am trying to do, but I want to store the result of my ajax request in a global variable. This would allow me to test the var before making the ajax call...and avoid repeated ajax calls for the same data. I need to be able to pass the variable name from the click event down through the populateSelect function into the ajaxCall function.
It seems like I could pass a function as a parameter, but I have not been able to make that work.
I like to include working examples in my questions, but in this case the latency in the call to the server is part of the problem.
Thanks
$('#getSelectOptions').bind("click", function() {
populateSelect(this);
});
function populateSelect(whatWasClicked) {
var thisSelect = $(whatWasClicked).parents("div").find("select") ;
var before = function() { $(loading).show() ; } ;
var complete = function() { $(loading).hide() ; } ;
var data = {'_service' : 'myService', '_program' : 'myProgram' } ;
var error = function(){alert("Error"); } ;
var success = function(request) { $(thisSelect).html(request) ; };
var waitTime = 20000 ;
ajaxCall(thisSelect, waitTime, before, complete, data, success, error ) ;
}
function ajaxCall(elementToPopulate, waitTime, whatToDoBeforeAjaxSend,
whatToDoAfterAjaxSend, dataToSendToTheServer,
whatToDoAfterSuccess, whatToDoAfterError) {
$.ajax({
type: "post",
url: "http://myURL/cgi-bin/broker",
dataType: "text",
data: dataToSendToTheServer,
timeout: waitTime,
beforeSend: whatToDoBeforeAjaxSend,
error: whatToDoAfterError(request),
success: whatToDoAfterSuccess(request)
});
}
EDIT Further education in how to write a good question... I should have mentioned that I call populateSelect to populate multiple selects..so I need way to reference the results for each select
jQuery has a $.data method which you can use to store/retrieve items related to any element on the page.
//e.g. create some object
var inst = {};
inst.name = 'My Name'
var target = $('#textbox1');
//save the data
$.data(target, 'PROP_NAME', inst);
//retrieve the instance
var inst = $.data(target, 'PROP_NAME');
It looks like in the example you gave, you only have one type of AJAX request, POSTed to the same URL with the same data every time. If that's the case, you should just need something like :
var brokerResponse = null; // <-- Global variable
function populateSelect(whatWasClicked) {
var thisSelect = $(whatWasClicked).parents("div").find("select") ;
if (!brokerResponse) { // <-- Does an old response exist? If not, get one...
var before = function() { $(loading).show() ; } ;
var complete = function() { $(loading).hide() ; } ;
var data = {'_service' : 'myService', '_program' : 'myProgram' } ;
var error = function(){alert("Error"); } ;
var success = function(request) { // <-- Store the response before use
brokerResponse = request;
$(thisSelect).html(brokerResponse);
};
var waitTime = 20000 ;
ajaxCall(thisSelect, waitTime, before, complete, data, success, error ) ;
}
else { // <-- If it already existed, we get here.
$(thisSelect).html(brokerResponse); // <-- Use the old response
}
}
If you have multiple possible items for whatWasClicked which each need a different AJAX response cached, then you need to have some string with which to identify whatWasClicked, and use that to store multiple values in your global variable. For example, if you have a unique id on whatWasClicked, this would work:
var brokerResponse = {}; // Global variable is a simple object
function populateSelect(whatWasClicked) {
var whatWasClickedId = $(whatWasClicked).attr('id'); // Get the unique ID
var thisSelect = $(whatWasClicked).parents("div").find("select") ;
if (!brokerResponse[whatWasClickedId]) { // Check that ID for a response
var before = function() { $(loading).show() ; } ;
var complete = function() { $(loading).hide() ; } ;
var data = {'_service' : 'myService', '_program' : 'myProgram' } ;
var error = function(){alert("Error"); } ;
var success = function(request) {
brokerResponse[whatWasClickedId] = request; // Using ID
$(thisSelect).html(brokerResponse);
};
var waitTime = 20000 ;
ajaxCall(thisSelect, waitTime, before, complete, data, success, error ) ;
}
else {
$(thisSelect).html(brokerResponse[whatWasClickedId]); // Etc...
}
}
JavaScript's scoping is such that if you just declared a global variable, you should be able to access it from within the ajax success function and the click function as well.
var _global_holder = null;
$('#getSelectOptions').bind("click", function() {
if(_global_holder==null) { whatever }
populateSelect(this);
});
function populateSelect(whatWasClicked) {
if(_global_holder !== null) {
whatever
} else { whatever else }
ajaxCall(thisSelect, waitTime, before, complete, data, success, error ) ;
}
function ajaxCall(elementToPopulate, waitTime, whatToDoBeforeAjaxSend,
whatToDoAfterAjaxSend, dataToSendToTheServer,
whatToDoAfterSuccess, whatToDoAfterError) {
...
}

Categories