julik live

Sequencing AJAX requests on "send-last basis"

So imagine you have this autocomplete thingo. Or a dynamic search field. Anything that updates something via AJAX when it's value changes.

Now, it's tempting to do this (I am speaking prototype.js here, jQuery would be approximately the same.

    function updateResults(field) {
        new Ajax.Request('/s', {
            method:'get',
            parameters: {"term": field.value},
            onSuccess: function(transport){
                var archiveList = transport.responseText.evalJSON();
                displayResults(archiveList); // This updated the DOM
            }
        });
    }

    new Form.Element.Observer(searchField, 0.3, function(form, value){
        updateResults(form);
    });

However, we got a problem here. Imagine that the user types something, and the observer fires when the field contains some. Now, logically some would find more entries than something and probably the search (the request) will take longer. So if you visualise the call timeline here's what you will see (pardon my ASCII):

            |                    |                           ^                    ^
            GET /s?term=so       |                           |             Update DOM for "so"
            |                GET /s?term=some           Update DOM for "some"     |
            |                    |===========================|                    |
            |                                                                     |
            |=====================================================================|

So the more specific request will complete faster and will update the DOM first. However, after some seconds when the previous request completes the previous request will overwrite the DOM again with less specific results.

What you need to do to prevent that is to discard the requests that have been sent before the last one. To do this, use a recorded sequence number for the request per window, and discard the results that come too late. Of course this will not stop the client from pounding on the server (ultimately you will need clever tricks like caching responses, increasing observe intervals and such) but it will at least ensure that the user is looking at the most specific result.

    function updateResults(field) {

        // First init our counter
        if (!window._reqSeq)  window._reqSeq = 0;

        // Increment request counter
        window._reqSeq +=1;

        // Store the sequence number of this request
        var thisRequestOrderNo = window._reqSeq;

        new Ajax.Request('/s', {
            method:'get',
            parameters: {"term": field.value},
            onCreate: showSpinner,
            onSuccess: function(transport){
                // ...and if this request is not the last one sent
                // discard it's payload.
                if(thisRequestOrderNo < window._reqSeq) {
                    console.debug("Request was stale, skipping");
                    return;
                }
                var archiveList = transport.responseText.evalJSON();
                updateDocument(archiveList);
            },
        });
    }

This works since now the callgraph will look like this:

            |                    |                           ^                    
            GET /s?term=so       |                           |             
            |                GET /s?term=some           Update DOM for "some"     
            |                    |=============(seq:3)=======|                    X
            |                                                         onSuccess returns early
            |==================================(seq:2)=============================|

Hat tip to Orbling for his SO answer on this.

Suspects: Веб-стройка

 
comments powered by Disqus

Aspirine not included.