Search code examples
javascriptjqueryajaxgetlivesearch

How to queue ajax request while the server is still processing previous request?


I'm performing a live server side search while a user is typing in the browser. My seach functionality can't keep up with the typing pace, so requests pile up.

I want to be able to cancel queries that are replaced by new queries before the server is ready to respond.

This is what I want to achieve:

  • The first keystroke should be sent to the server.
  • The next key stroke should be queued by the client side script until the server responds to the first query.
  • If, in the meantime, another keystroke happens it should replace the currently queued keystroke.

This way, only the most recent keystroke gets sent to the server, once the server ready. Of course, if there is no queue, a keystroke should just be sent straight to the server.

My current implementation is dead simple:

$.ajax({
  type: "get",
  dataType: "script",
  data: {
    search: "something"
  },
  url: "/api/search",
  success: function(data, status, xhr) {},
  error: function(xhr, textStatus, errorThrown) {}
});

Solution

  • I think your approach is going to flood the server, because you're sending a request to the server on each keystroke. And even if you abort the request on the client side as @SravanS said, the server will still receive and start to process all of them. The HTTP request once sent is already on the net, you can't stop it, what you can do is no the client side abort it, which i assume notifies the server or just ignores the response it sends, but still you're flooding it.

    Maybe implementing a delay to recognize when the user stopped typing would be the best approach. This is a generic delayed event handler factory, really easy to use, just pass your function to it and assign it a delay. If X miliseconds passed between keystrokes, a request will be sent, but if another keystroke happens before that delay, you're not even going to make a request.

    function delayedEvent(eventHandler, delay) {
        var lastTimeout = null;
    
        return function () {
            var that = this,
                args= Array.prototype.slice.call(arguments).sort();
    
            if (lastTimeout !== null)
                window.clearTimeout(lastTimeout);
    
            lastTimeout = window.setTimeout(function () {
                eventHandler.apply(that, args);
            }, delay);
        };
    }
    
    $('... selector ...').on('event', delayedEvent(function () { ... your code ... }, 500));
    

    EDIT: This is how you can implement the queue, i haven't tested this code, use it as a starting point.

    function eventQueue(requestMaker) {
        // Here we store the last event queued, it'll wait until all the already running events are done.
        var lastQueued = null,
            runningQueue = [];
    
        // Push a new event into the running queue
        function pushRunning(toPush) {
            runningQueue.push(toPush.done(
                // It is necesary to use an IIFE to get the index by value and not by reference
                (function (index) {
                    return function() {
                        // Remove this event from the runningqueue
                        runningQueue.splice(index, 1);
                        // If the queue has been completed, start running the last event sent
                        if (lastQueued !== null && runningQueue.length === 0) {
                            pushRunning(lastQueued());
                        }
                    }
                }(runningQueue.lenght));
            ));
        }
    
        return function () {
            var that = this,
                args = Array.prototype.slice.call(arguments).sort();
    
            // Some events are already running, just override the lastQueued
            if (runningQueue.length > 0) {
                lastQueued = requestMaker.bind(that, args);
            } else {
            // Nothing queued run this event straight away, and queue it as running
                pushRunning(requestMaker.apply(that, args));
            }
        };
    }
    
    $('... selector ...').on('event', eventQueue(function () { 
        // It's extremely important that you return the jquery $.ajax Promise
        return $.ajax(...);
    }, 500));