Конкурирующие асинхронные запросы

Никогда нельзя предсказать в какой последовательности будут отрабатываться асинхронные запросы. Зачастую важен только ответ на самый последний запрос, а предыдущие запросы можно просто проигнорировать.

var concurrent = (function () {
    var concurrentRequests = {};
    return function (opts, cache) {
        // вместо jqXHR функция будет возвращать promise-объект
        var deferred = $.Deferred(), promise = deferred.promise();

        // сохраняем колбеки, если они есть
        promise.done(opts.success || $.noop);
        promise.fail(opts.error || $.noop);
        promise.always(opts.complete || $.noop);

        // удаляем колбеки из параметров,
        // так как cacheable не обрабатывает их
        delete opts.success;
        delete opts.error;
        delete opts.complete;

        // таймстемп запроса.
        // ответы с таймстемпом меньше текущего игнорируются
        var requestId = concurrentRequests[opts.url] = $.now();

        // кеширующий или обычный запрос
        (cache ? cacheable : $.ajax)(opts)
            .done(function () {
                if (concurrentRequests[opts.url] <= requestId) {
                    deferred.resolveWith(promise,
                        Array.prototype.slice.apply(arguments));
                }
            })
            .fail(function () {
                if (concurrentRequests[opts.url] <= requestId) {
                    deferred.rejectWith(promise,
                        Array.prototype.slice.apply(arguments));
                }
            });

        return promise;
    }
}());

В паре с функций cacheable получилась достаточно универсальная замена традиционной функции $.ajax.

$(".list").each(function () {
    var list = $(this);
    list.on("click", ".list__page", function () {
        concurent({
            url: "/list/",
            data: {page: $.tim($(this).text())},
            dataType: "html"
        }, true).done(function (markup) {
            list.html(markup);
        });
    });
});