php - Javascript: why is my instant search function running lagging, how to improve?
The demo below is the instant search function I build, just like google search, printing out result right after the user's input.
When testing, it runs smoothly on localhost, but on the web, it runs lagging, that is, when user typing in multiple characters fast, the keypress will not show in input box for some seconds to wait for data processing (I guess) then the charactered typed showed many seconds after . Absolutely this is no good for user's experience. How may I resolve this?
Frontend
<input onkeyup="searchq();" type="text">
<script>
function searchq(){
// get the value
txt = $("input").val();
// post the value
if(txt){
$.post("search.php", {searchVal: txt}, function(result){
$("#search_output").html(result+"<br><a class='anchor_tag_s' href='createobject.php?object="+txt+"'><div id='notfound'>Not found above? Create Here.</div><a>");
});
}
else{
$("#search_output").html("");
}
};
</script>
Backend
//query the search using match
$query=mysqli_query($conn,"SELECT * from objects WHERE Match(name) Against ('%$search%' in natural language mode) LIMIT 9") or die("could not search! Oops, Panpan might be hacked");
$count=mysqli_num_rows($query);
//if match no result using like query
if($count==0){
$query=mysqli_query($conn,"SELECT * from objects WHERE name LIKE '%$search%' LIMIT 9") or die("could not search! Oops, Panpan database might be hacked");
$count=mysqli_num_rows($query);
if ($count==0){
while($count==0){
//if matach and like show no result, remove the frist word of array, then search, if no result, do it again, if not, do it again.
$search = explode(" ",$search);
$search=array_slice($search,1,2);
$search=implode(" ",$search);
$query=mysqli_query($conn,"SELECT * from objects WHERE name LIKE '%$search%' LIMIT 9") or die("could not search! Oops, Panpan database might be hacked");
$count=mysqli_num_rows($query);
while($row=mysqli_fetch_assoc($query)){
$object_id=$row["object_id"];
$object_name=$row["name"];
$object_description=$row["description"];
$object_up=$row["up"];
$object_down=$row["down"];
$object_views=$row["views"];
$output=$object_name;
$result= "<a class='anchor_tag_s' href='"."object.php?id=".$object_id."'><div>".$output."</div></a>";
echo $result;
}
}
}
else{
// print out the like query
while($row=mysqli_fetch_assoc($query)){
$object_id=$row["object_id"];
$object_name=$row["name"];
$object_description=$row["description"];
$object_up=$row["up"];
$object_down=$row["down"];
$object_views=$row["views"];
$output=$object_name;
$result= "<a class='anchor_tag_s' href='"."object.php?id=".$object_id."'><div>".$output."</div></a>";
echo $result;
}
}
}
else{
// print out the match query
while($row=mysqli_fetch_assoc($query)){
$object_id=$row["object_id"];
$object_name=$row["name"];
$object_description=$row["description"];
$object_up=$row["up"];
$object_down=$row["down"];
$object_views=$row["views"];
$output=$object_name;
$result= "<a class='anchor_tag_s' href='"."object.php?id=".$object_id."'><div>".$output."</div></a>";
echo $result;
}
}
}
?>
Answer
Solution:
I'd suggest absolutely not searching directly on key-up. That's incredibly inefficient and very resource intense. I'd suggest setting a timer for say, one second. Then, on key-up, you refresh that timer to one second. If it ever reaches 0, then you search. Something like this:
Set the timer to whatever you want, in milliseconds. I recommend one second, but you can lower it as you see fit.
Check the fiddle here:
https://jsfiddle.net/kbyy2m6u/
What the code is doing
Right off the bat, we set our timer to 1000 milliseconds and
started
tofalse
.The
keyup
inline-function is resetting the timer to 1000 milliseconds, queues the timer, and setsstarted
totrue
. We set it totrue
so thequeueTimer()
function does not get queued multiple times (which would negate the entire point of queueing instant searches). Once it's set totrue
, it will not runqueueTimer()
until the timer runs out and a search is actually conducted.queueTimer()
is the bread and butter here. If the timer has not run out yet, it will simply decrease thetimer
variable by 100 milliseconds, and call itself to be ran again in 100 milliseconds (looping until the timer runs out). Once it runs out, it executes yoursearchq()
and setsstarted
tofalse
again so the instant search can run once more.In plain English, every time someone types in a search box it will wait one second before doing an instant-search. The timer will reset on key-up, even after the queue is started, so that while typing a person will not be searching. This prevents multiple erroneous searches by making sure a user has paused for a bit to actually conduct a search.
Now a word of caution
Google is a giant - they make magical things happen on the web. Things like an instant search are great in theory, but for those of us who aren't able to throw billions of dollars at R&D like Google, it's something that's not typically worth the headache.
Every single call you make to the database is taxing. If you've got other functionality on your web application that hits the DB and now you're adding an instant search, be prepared for some pretty interesting side-effects. They may even be unforeseen side effects on totally separate databases due to data locks on your MySQL Engine. This is one reason why Data Locking is so prevalent in a lot of Magento installs.
I'm sure you know what you're doing, but I want to make sure anyone who sees this question will realize the trade-off to something like instant search.
Answer
Solution:
You should consider two things: debounce your calls and abort any previous ajax requests.
For the first one, there are lots of information on debounce/throttle techniques. Basically you have to assure that you do not execute more than one function call (
searchq
) in a certain period of time. Thus, if the user typesabc
, it won't trigger 3 different searches fora
,ab
andabc
. Instead, it will trigger a single search for the whole result (abc
).Another thing you should consider is aborting unresolved ajax calls.
Since you're relying on ajax calls to show results, and since ajax calls are... well... asynchronous, you shouldn't actually have multiple simultaneous ajax calls. Take the following example:
ab
searchq
function is executed for the first character (a
)searchq
function is executed for the new sequence (ab
)To avoid this issue, you should abort - or ignore at least - any other ajax call that is in progress by the time you fire a new one.