I have a textbox that for each time a user inputs a letter I make a search with an ajax request and show the result "live" for the user. Often when a user types the letters it takes longer time for the request to be made than for the user to input a new letter, hence a new request is made before the first one have ended. It would be much better if the first one could end before I do the next request. Is there a good way to only make a new request if the latest request have ended?
This is my jquery code:
$("#MyTextBox").change(function() {
if (this.value.length > 2) {
$.ajax({
type: "GET",
url: "http://www.domain.com/Service.svc/Search?keyword=" + this.value,
dataType: "json",
success: function(data) {
//here I have some code that shows the result for the user
}
});
}
});
You could create a boolean that will hold true or false depending on if there is a request already happening. Set it to true when you start a request and set it back to false in your callback function.
var request_in_process = false;
$("#MyTextBox").change(function() {
if (this.value.length > 2 && !request_in_process) {
request_in_process = true;
$.ajax({
type: "GET",
url: "http://www.domain.com/Service.svc/Search?keyword=" + this.value,
dataType: "json",
success: function(data) {
request_in_process = false;
//here I have some code that shows the result for the user
}
});
}
});
You can abort an AJAX request. Keep track of the request as a variable, and abort it before re-initiating the request.
var request = $.ajax({ ... });
request.abort();
This has the added advantage of being more responsive to user input. If the user has typed something more since the first request was initiated, he probably doesn't care about the first set of results anymore. Aborting and recreating the AJAX request means the user gets a better set of results back.
You can utilize ajaxComplete so that you know when a request has finished before making a new request.
You can combine this with bind/unbind so that the change event is added once manually, then unbound when an ajax request starts, and rebound when the ajax request finishes.
As ceejayoz already mentioned, you should abort the previous request. (Waiting for the request will make the users mad if they can't type as they want. And prefering the older requests over the newer ones makes the results outdated.) So what about:
var request = null;
//...
$("#MyTextBox").change(function() {
if (this.value.length > 2) {
if (request && request.abort)
request.abort();
request = $.ajax({
type: "GET",
url: "http://www.domain.com/Service.svc/Search?keyword=" + this.value,
dataType: "json",
success: function(data) {
//here I have some code that shows the result for the user
request = null;
});
}
});
Here's slightly more robust implementation. Basically we save the ajax object (i.e. promise object) in to closure so that subsequent calls can look at it. If next calls finds that the status of request is pending then it will add callbacks to that pending request instead of generating new request. jQuery will fire all callbacks in same sequence they were added. However there is possibility that same caller is trying to create request again and again. To protect against that I've added callerId parameter. If same caller has made request again while previous request was still pending then we just ignore that request. For completeness I've also added cachedValues. So if ajax request was already made, we don't make it again.
var ajaxRequestor = function ($) {
"use strict";
//privates
var cahedValues = {};
var currentAjaxRequest, callers;
//public interface
return {
getData: function (callerId, onGet, onFail, forceRefresh) {
if (forceRefresh || !cahedValues.myData) {
//If there is a pending request, don't start new one
if (!currentAjaxRequest || currentAjaxRequest.state() != "pending") {
currentAjaxRequest = $.getJSON("data/MyData.json");
callers = {}; //list of callers waiting for request
}
//if this caller is not in the list, add it and queue up its callbacks to existing request
if (!callers[callerId]) {
callers.callerId = callerId;
currentAjaxRequest.done(function (data, textStatus) {
cahedValues.myData = data;
onGet(data);
});
currentAjaxRequest.fail(function (jqxhr, textStatus, error) {
if (!!onFail) {
onFail(error);
}
else throw error;
});
}
}
else {
onGet(cahedValues.myData);
}
},
invalidateMyData: function () {
delete cahedValues.myData;
}
};
})($);