How to resolve race conditions on debounced user input? - javascript

For example, say a page returns search results based on debounced user text.
How do you account for the case where an endpoint has a highly variable latency in which the second call can return before the first call.
E.g.
User is typing "books and movies" with a keyup debounce of 500ms
The user slightly pauses in the middle so the string is "books", this triggers a search call.
The user continues typing and finishes, triggering the second call with "books and movies".
The second call returns first, populating the list based on "books and movies".
Then the first call, which was delayed comes back and re-renders the list based on "books".
The user sees only "books" and is confused.
The surefire way to solve this is with a button to manually trigger the call. I'd like to avoid this though so I've increased the debounce but I'm wondering if there's a better way.

We suppose that you use jQuery to make ajax calls.
One solution is to use a pooling system: basically an array containing ajax requests.
Each time, a new request is emitted, you abort all request in the pool.
So you ensure that the last request made will be the only one that will end.
Here is the implementation of the pool:
jQuery.xhrPool = [];
jQuery.xhrPool.abortAll = function () {
jQuery(this).each(function (idx, jqXHR) {
jqXHR.abort();
});
jQuery.xhrPool.length = 0;
};
Here is an example on how to use it with the "search repository API" from GitHub (https://developer.github.com/v3/search/#search-repositories):
jQuery.xhrPool = [];
jQuery.xhrPool.abortAll = function () {
jQuery(this).each(function (idx, jqXHR) {
jqXHR.abort();
});
jQuery.xhrPool.length = 0;
};
$(document).ready(function(){
$("#SearchField").autocomplete({
source: function( request, response ) {
// First we abort all other request
jQuery.xhrPool.abortAll();
$.ajax({
url: "https://api.github.com/search/repositories",
method: "get",
dataType: "jsonp",
data: {
q: request.term
},
beforeSend: function (jqXHR) {
// Before sending the request we add it to the pool.
jQuery.xhrPool.push(jqXHR);
},
success: function(data) {
var items = new Array();
for(var i=0;i<data.data.items.length;i++)
{
items.push(data.data.items[i].name);
}
response(items);
}
});
},
minLength: 3,
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.css" >
<input type="text" id="SearchField" />

Javascript works synchronously so there is no possibility of race conditions if you write your code correctly.
I guess you are using ajax (ajax is supposed to be async, don't use sync ever, once you go sync, you can't go back) to get the query result. You are probably using some code like this:
var req=new XMLHttpRequest();
req.onreadystatechange=function(){
if (req.readyState==4){
if (req.status==200){
// Your callback here which shows autocomplete suggestions maybe?
}
}
}
Hold on to that req variable. So once you do a new request, you can simply discard the old request like:
req.onreadystatechange=null;
You can also abort the ajax request like:
req.abort();

Related

ASP.net Core MVC : How to fix looped results?

I have a problem when saving items on my application, items are listed from a table.
Everytime I save the updated items , the items tend to loop after saving.
This is my event of the button.
$('#saveEventBTN').click(function (event) {
//EventManager.SaveEventBYApprover();
EventManager.MultipleSave();
event.preventDefault();
});
This one is from the .Change event on the dropdown menu where I can enter the ids in the arrays that I will use for the looping saving transaction.
$("#tableApprovalState" + idEvent + "").change(function (event) {
idApproveStatus = $("#tableApprovalState" + idEvent + "").val();
event.preventDefault();
// Adding the event into an array for multiple selection.
if (idEventMulti.indexOf(idEvent) !== -1)
{
var del = idEventMulti.indexOf(idEvent);
idEventMulti.splice(del, 1);
idApproveStatusMulti.splice(del, 1);
idEventMulti[idEventMulti.length] = idEvent;
idApproveStatusMulti[idApproveStatusMulti.length] = idApproveStatus;
} else {
idEventMulti[idEventMulti.length] = idEvent;
idApproveStatusMulti[idApproveStatusMulti.length] = idApproveStatus;
}
});
This are the functions on my module :
This only loops the global array variable for the ids of the array.
EventManager.MultipleSave = function () {
for (let i = 0; i < idEventMulti.length; i++) {
EventManager.SaveEventBYApprover(idEventMulti[i], idApproveStatusMulti[i]);
}
}
This one is the Ajax call to save:
EventManager.SaveEventBYApprover = function (idevent, ideventstatus) {
$.ajax({
url: "/Modules/SaveEvent",
type: "POST",
data: {
IdEvent: idevent,
IdEventStatus: ideventstatus,
LastModifiedBy: user
},
dataType: "json",
success: function (data) {
$("#successMsg").html("").html(data.msg);
$("#successConfirmModal").modal("show");
searchResultsTable.clear().draw();
EventManager.BindDataTable();
}
});
}
This is how it looks like after the transactions successfully save.
I think you're correct in assuming the problem is called by multiple calls to EventManager.BindDataTable in the ajax success callbacks.
It's not a solution to call this method after the for loop in EventManager.MultipleSave, because this code will be called immediately after starting the ajax calls, which are asynchronous.
What you want is to set up some code that runs after all the asynchronous save ajax calls have completed. This is quite messy to achieve with the current callback model you're using.
Instead you can exploit that the $.ajax function returns a Promise. This means that if you return the result of $.ajax from EventManager.SaveEventBYApprover, you can collect these in an array in EventManager.MultipleSave, and then use something like:
$.when(myAjaxPromises).then(function() {
// code that runs after all saves goes here
}, function() {
// error handling goes here
});

Push item into new array within Ajax method

I have the following array
var arrayOfResults = []; // Results after like statement
I make a call to the database which returns me a json result as shown here:
[{
"id": "{fcb42c9c-3617-4048-b2a0-2600775a4c34}",
"pid": "{34214CCB-90C3-4D75-958B-5A1D0FBDD971}",
"ttl": "Easter Bunny",
"img": "/~/media/Images/Recipes/Easter/Filled Pasta/LF_Baked-Spring-Vegetables-Ravioli_920.ashx?h=910\u0026w=910",
"url": "Some url",
"taggedwith": ["{3A54907D-4171-4F4E-8FE8-3A38DA1E874F}", "{6CD78C6B-F435-45EC-BE16-810E80311C23}", "{74528A6F-C40B-4030-A278-A4C9A2F46A47}", "{6DC82B78-61F6-45A0-A63C-EA590BB1057E}", "{E9EF1A41-51D0-403D-9373-37B7A880B251}"],
"articleddate": "2015-05-02",
"tname": "Recipe",
"rbrand": ["{1F6EDA5D-4681-40F0-B455-7C343AC25B72}"]
}, {
"id": "{2e4b04b6-334f-42e9-afd7-ddc4e08417ad}",
"pid": "{C611BAC8-E8E0-4693-920B-93BD5EE2386B}",
"ttl": "Latina Fettuccini \u0026 Summer Sauce with Prawns Recipe",
"img": "/~/media/Images/Recipes/Latina Fresh/Plain Pasta/LF_Fettuccini-Summer-Sauce-Prawns_920.ashx?h=910\u0026w=910",
"url": "Some url",
"taggedwith": ["{3A54907D-4171-4F4E-8FE8-3A38DA1E874F}", "{6CD78C6B-F435-45EC-BE16-810E80311C23}", "{74528A6F-C40B-4030-A278-A4C9A2F46A47}", "{6DC82B78-61F6-45A0-A63C-EA590BB1057E}", "{E9EF1A41-51D0-403D-9373-37B7A880B251}"],
"articleddate": "2015-05-02",
"tname": "Recipe",
"rbrand": ["{1F6EDA5D-4681-40F0-B455-7C343AC25B72}"]
}]
On the UI I have a text field, which the user can enter free text.
I call the following ajax method when the user has entered roughly 5 characters, what I'm trying to achieve is I want to perform a like statement on the ttl field within the above array, If the ttl field matches or is like the freeText the user has entered then I want to push that item in to the array 'arrayOfResuts' however I see the alert message found yet it doesn't push the item into the new array, I know this because I alert the length at the end of the ajax call and its 0;
var addItem = false;
var freeText = $('#searchKeywords').val();
$.ajax({
url: 'search?t=&s=DateDesc&type=globalsearch&q=',
type: 'GET',
dataType: 'json',
success: function (searchDataList) {
console.log(searchDataList)
for (var i = 0; i < searchDataList.length; i++) {
addItem = false;
if (freeText.length > 0) { // Filter on free text
if (searchDataList[i].ttl.indexOf(freeText) > -1) { // if title contains free text then we need to add it to the arrayOfResults[].
alert('found');
arrayOfResults.push(searchDataList[i]) // This doesn't seem to work.
addItem = true;
}
}
} // End of for loop
},
error: function (request, error) {
}
});
alert(arrayOfResults.length);
Now I'm not 100% sure what's actually going wrong, so any help would be appreciated.
Your alert is running before your AJAX request is complete.
Since the AJAX request is asyncronous the console.log() code runs before the success is called and so your not printing the result you want.
To print the results simply print within the success and error functions of the AJAX request. Doing so in the complete function will not help since it runs asyncronously from the others.
As the other answers mentioned you need to handle the data within the success block. You can do it directly like below, or for more complex cases call a new function to handle the data at the end of the success statement.
Update code:
var freeText = $('#searchKeywords').val();
$.ajax({
url: 'search?t=&s=DateDesc&type=globalsearch&q=',
type: 'GET',
dataType: 'json',
success: function (searchDataList) {
console.log(searchDataList);
for (var i = 0; i < searchDataList.length; i++) {
addItem = false;
if (freeText.length > 0) {
if (searchDataList[i].ttl.indexOf(freeText) > -1) {
alert('found');
arrayOfResults.push(searchDataList[i]);
addItem = true;
}
}
} // End of for loop
if (arrayOfResults.length > 1) {
alert(arrayOfResults.length);
console.log(arrayOfResults);
}
},
error: function (request, error) {
}
});
Your alert will always be 0 because AJAX is asynchronous (the A in AJAX stands for that) and you call your alert synchronously.
What happens is, the ajax request gets the data while the synchron code runs as usual. Now that the ajax request is done clearly after you call your alert it cannot log anything that makes sense.
If you want a function to be called when the asynchronous request is done, wheter it was successful or not, use done: additionally to success: and error:. This code will run when the request is done and show you the actual length.

Loop ajax request one by one and show result one by one?

I am trying to check whether some email addresses are registered on a website or not with cURL function.
So in this case I have 3 textarea elements. The first contains lists of email, the second contains live email (indicated that email is registered) and the third contains unregistered email. Then there is button called "Check", if user clicks this then it makes an AJAX request, one after another, and shows result one after another, instead of multiple AJAX request in one time (async: true) or one by one AJAX request but one time result at the end of request (async: false).
The problem if I use async: true my browser will crash for big lists and if I use async: false I don't know whether my application is running or not since it shows the result at the end of request, even request is one after one.
Then so to make my question simple here is my code.
$("div#check").click(function(){
//Assume i just grab email lists from text area and put in array
var mail_lists_clean = ["a#yahoo.com", "b#aol.com", "c#gmail.com"];
var promises = [];
$.each(mail_lists_clean, function(index, value){
var promise = $.ajax({
method: "POST",
url: "valid_check.php",
//cache: false,
async: false,
data: {
email: value
}
}).success(function(data) {
if (data == "live") {
//append email to second textarea
}
else {
//append email to third textarea
}
});
promises.push(promise);
});
$.when.apply($, promises)done(function() {
alert("All Request done!");
}).fail(function() {
console.log("error");
});
});
Can anyone help me make AJAX requests one after another and show results one after another? Please correct my code above. Thanks.
To make ajax call one by one
you need to make recursion call at $.ajax success callback function.
function ProcessEmailList(mail_list) {
// process one email at a time
var value = mail_list.pop();
// if there is email in the array
if (value) {
$.ajax({
method: "POST",
url: "valid_check.php",
async: false,
data: {
email: value
}
}).done(function(data) {
if (data == "live") {
//append email to second textarea
} else {
//append email to third textarea
}
// recursion call, process rest of the email
ProcessEmailList(mail_list);
}).fail(function() {
console.log("error");
});
} else {
// no more email in the array, we have processed all
alert("All Request done!");
}
};
$("div#check").click(function() {
//Assume i just grab email lists from text area and put in array
var mail_lists_clean = ["a#yahoo.com", "b#aol.com", "c#gmail.com"];
ProcessEmailList(mail_lists_clean);
});
Why not pass All the emailadresses at once in a single ajax request separated by a certain character and pass the result back? That way long lists of addresses wont be a problem and it Will save You a lot of hassle of figuring out which request was handled and which one wasnt. It would Be better for performance as well.

Javascript autocomplete with ajax call order

I have simple autocomplete input field with Javascript like this:
$('#search').on('keyup', function () {
var query = $(this).val();
$.ajax({
type: "GET",
url: "/search",
data: { query: query }
}).done(function (results) {
showSearchResults(results);
});
});
Sometimes first call takes more time then second or third and results are overridden.
How can I make sure that results only from the latest successful call are displayed?
I mean if I got response from call #3 - I no longer care about calls #1 and #2 and don't want them to override results of call #3.
Ajax function is in default asynchronous it means that many of functions can run on same time. If You wrote 3 letters it will run 3 times, after 3 keyups. If you want to run function in sequence just add setting async: false.
$.ajax({
type: "GET",
url: "/search",
async: false,
data: { query: query }
}).done(function (results) {
showSearchResults(results);
});
But i think You should add some delay, so function will not run immediately after every keyup, just after last one.
I suggest that you bind an incremental id to each ajax request that you send. When you get a response, just check that it carries last given id.
let lastXHRid=0; // tracker of last sent ajax request
$('#search').on('keyup', function () {
let reqXHR = $.ajax({ // create a variable out of $.ajax returned value
type: "GET",
url: "/search",
data: { query: $(this).val() }
});
lastXHRid++; // increment the XHR counter
reqXHR.id = lastXHRid; // attach id to the request
reqXHR.done(function(results, status, respXHR) {
if ( respXHR.id == lastXHRid ){ // compare id of received and last sent requests
showSearchResults(results);
}
});
});
(Edit: I initially suggested tracking the unix timestamp of last sent request, but as #seth-battis suggested in the comments, a sequence number is far enough. As a bonus, I also debugged my sample snippet!)

How to stop all active or pending ajax requests?

In my rails app, using jquery slider. In stop event of slider, there is ajax request. If user continuously sliding slider there are too many pending ajax requests and my system get hang. I have used:
1:
function slide_stop(event, ui){
$.xhrPool = [];
$.xhrPool.abortAll = function() {
$(this).each(function(idx, jqXHR) {
jqXHR.abort();
});
$.xhrPool.length = 0
};
$.ajaxSetup({
beforeSend: function(jqXHR) {
$.xhrPool.push(jqXHR);
},
complete: function(jqXHR) {
var index = $.xhrPool.indexOf(jqXHR);
if (index > -1) {
$.xhrPool.splice(index, 1);
}
}
});
$.xhrPool.abortAll();
$('span#imgLoader').html('<img src="/assets/ajax-loader.gif">');
$.ajax({
type: 'get',
dataType: 'json',
url: 'some_url',
data: { is_ajax: true }
}).done(function(response){
$('span#imgLoader').empty();
});
}
Initialize slider,
$elt.slider({
min:0,
max:100,
value:50,
slide: slide_range,
stop: slide_stop
}).each(function(){
add_range_label($range_elt);
});
All ajax requests get stopped/in not modified state. But last request takes too long time to complete. No result again same hanging state.
2:
var request = $.ajax(
{
type: 'POST',
url: 'someurl',
success: function(result){}
});
then,
request.abort();
Not working. Too many requests are still in pending state.
I don't know what is wrong with it.
I tried using 'jquery-throttle-debounce'. Included file 'jquery.ba-throttle-debounce.min.js'
Applied jQuery.debounce to stop event for slider.
$("#slider").on( "slidestop", $.debounce( 240, slide_stop ) );
I tried by reducing time delay. But no expected result. Same case as above.
You can use a plugin such as http://archive.plugins.jquery.com/project/AjaxManager.
Also, as Javis mentioned you are best off to also incorporate a delay before sending the response so that you don't needlessly overload your server with requests. Instead of using time differences or arrays I would just keep a reference to the last ajax request created. You would abort that request each time a new one is made.
// just putting these here for example, you would want to associate them with each instance of your slider control.
var timeout, last_request;
function slide_stop(event, ui){
$('span#imgLoader').html('<img src="/assets/ajax-loader.gif">');
// clear the timeout so a previous request won't be sent
clearTimeout(timeout)
// set the request to be sent in .5 seconds
timeout = setTimeout(send_request, 500);
}
function send_request(){
// abort the last request if there is one
if(last_request){
last_request.abort();
}
last_request = $.ajax({
type: 'get',
dataType: 'json',
url: 'some_url',
data: { is_ajax: true }
}).done(function(response){
$('span#imgLoader').empty();
// set the last request to null so that you dont abort a completed request
//(this might not be necessary)
last_request = null;
});
}
Try to throttle those requests, so many request to server can cause a performance issue on your server side, because remember that each ajax call is an apache (or whatever your webserver is) request, which costs memory and cpu. And remember that excesive requests can cause a DDoS.
Remember that as an user, i can start playing with the slider so much as i like, causing a ton of requests.
You should add something like:
var requests = [];
var lastCall = (+new Date()); // parenthesis just in case of minify
var timeDiff = 1000;
function sliderStopCallback() { // this will be called each time the slider stop sliding
if(timeDiff < 400) {
return;
}
abortAll();
requests.push( $.ajax({
type: 'POST',
url: 'someurl',
success: function(result){}
}) );
}
function sliderStartCallback() { // this will be call each time the user start to slide
timeDiff = (+new Date()) - lastCall;
lastCall = (+new Date());
}
function abortAll() {
var l = requests.length;
while(l--) {
requests[l].abort && requests[l].abort(); // the if is for the first case mostly, where array is still empty, so no abort method exists.
}
}
That way, each request will be sent at least on 400 milliseconds, preventing calls every 2 seconds.
This should work, but please notice that i havent tested it, so if you're going to try it please remember that it is a general idea, not a precise solution.
Good luck.
As said in comments, it's best to not send the request until the slideStop event. This way requests are not sent every time the value is changed, and thus server load is much less of an issue.
function ajaxrequest(){
$.ajax(....);
}
$("#slider").on("slideStop", function() {
ajaxrequest();
});

Categories