Buforowanie odpowiedzi jquery Ajax w javascript / przeglądarce

100

Chciałbym włączyć buforowanie odpowiedzi Ajax w javascript / przeglądarce.

Z dokumentacji jquery.ajax :

Domyślnie żądania są zawsze wysyłane, ale przeglądarka może udostępniać wyniki ze swojej pamięci podręcznej. Aby zabronić używania wyników z pamięci podręcznej, ustaw pamięć podręczną na wartość false. Aby żądanie zgłosiło błąd, jeśli zasób nie został zmodyfikowany od czasu ostatniego żądania, ustaw ifModified na true.

Jednak żaden z tych adresów nie wymusza buforowania.

Motywacja: chcę umieścić $.ajax({...})połączenia w moich funkcjach inicjalizacyjnych, z których niektóre żądają tego samego adresu URL. Czasami potrzebuję wywołać jedną z tych funkcji inicjalizacyjnych, czasami wywołuję kilka.

Dlatego chcę zminimalizować żądania do serwera, jeśli ten konkretny adres URL został już załadowany.

Mógłbym stworzyć własne rozwiązanie (z pewną trudnością!), Ale chciałbym wiedzieć, czy istnieje standardowy sposób zrobienia tego.

cammil
źródło
Nie sądziłbym, że trudno będzie śledzić, które adresy URL zostały już załadowane i przechowywać wyniki na tej liście. Następnie możesz sprawdzić swoje adresy URL przed wykonaniem połączenia AJAX. Voila - masz własną podstawową pamięć podręczną.
możesz dodać nagłówek cache-control i expires do swojej odpowiedzi na serwerze, więc twój serwer powinien być wywoływany tylko po przekroczeniu limitu czasu skonfigurowanego w tych wartościach
tutajandnow78
Jest bardzo długi, ale czy możesz mi pomóc zrozumieć, dlaczego potrzebowałeś pamięci podręcznej w żądaniach
Ajax

Odpowiedzi:

146

cache:true działa tylko z żądaniem GET i HEAD.

Możesz rzucić własne rozwiązanie, jak powiedziałeś, za pomocą czegoś w ten sposób:

var localCache = {
    data: {},
    remove: function (url) {
        delete localCache.data[url];
    },
    exist: function (url) {
        return localCache.data.hasOwnProperty(url) && localCache.data[url] !== null;
    },
    get: function (url) {
        console.log('Getting in cache for url' + url);
        return localCache.data[url];
    },
    set: function (url, cachedData, callback) {
        localCache.remove(url);
        localCache.data[url] = cachedData;
        if ($.isFunction(callback)) callback(cachedData);
    }
};

$(function () {
    var url = '/echo/jsonp/';
    $('#ajaxButton').click(function (e) {
        $.ajax({
            url: url,
            data: {
                test: 'value'
            },
            cache: true,
            beforeSend: function () {
                if (localCache.exist(url)) {
                    doSomething(localCache.get(url));
                    return false;
                }
                return true;
            },
            complete: function (jqXHR, textStatus) {
                localCache.set(url, jqXHR, doSomething);
            }
        });
    });
});

function doSomething(data) {
    console.log(data);
}

Pracuję tu na skrzypcach

EDYCJA: ponieważ ten post staje się popularny, oto jeszcze lepsza odpowiedź dla tych, którzy chcą zarządzać pamięcią podręczną limitu czasu, a także nie musisz przejmować się całym bałaganem w $ .ajax (), ponieważ używam $ .ajaxPrefilter () . Teraz wystarczy tylko ustawienie {cache: true}, aby poprawnie obsłużyć pamięć podręczną:

var localCache = {
    /**
     * timeout for cache in millis
     * @type {number}
     */
    timeout: 30000,
    /** 
     * @type {{_: number, data: {}}}
     **/
    data: {},
    remove: function (url) {
        delete localCache.data[url];
    },
    exist: function (url) {
        return !!localCache.data[url] && ((new Date().getTime() - localCache.data[url]._) < localCache.timeout);
    },
    get: function (url) {
        console.log('Getting in cache for url' + url);
        return localCache.data[url].data;
    },
    set: function (url, cachedData, callback) {
        localCache.remove(url);
        localCache.data[url] = {
            _: new Date().getTime(),
            data: cachedData
        };
        if ($.isFunction(callback)) callback(cachedData);
    }
};

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
    if (options.cache) {
        var complete = originalOptions.complete || $.noop,
            url = originalOptions.url;
        //remove jQuery cache as we have our own localCache
        options.cache = false;
        options.beforeSend = function () {
            if (localCache.exist(url)) {
                complete(localCache.get(url));
                return false;
            }
            return true;
        };
        options.complete = function (data, textStatus) {
            localCache.set(url, data, complete);
        };
    }
});

$(function () {
    var url = '/echo/jsonp/';
    $('#ajaxButton').click(function (e) {
        $.ajax({
            url: url,
            data: {
                test: 'value'
            },
            cache: true,
            complete: doSomething
        });
    });
});

function doSomething(data) {
    console.log(data);
}

A skrzypce tutaj OSTROŻNIE, nie działają z $ .Odroczonym

Oto działająca, ale wadliwa implementacja działająca z odroczonym:

var localCache = {
    /**
     * timeout for cache in millis
     * @type {number}
     */
    timeout: 30000,
    /** 
     * @type {{_: number, data: {}}}
     **/
    data: {},
    remove: function (url) {
        delete localCache.data[url];
    },
    exist: function (url) {
        return !!localCache.data[url] && ((new Date().getTime() - localCache.data[url]._) < localCache.timeout);
    },
    get: function (url) {
        console.log('Getting in cache for url' + url);
        return localCache.data[url].data;
    },
    set: function (url, cachedData, callback) {
        localCache.remove(url);
        localCache.data[url] = {
            _: new Date().getTime(),
            data: cachedData
        };
        if ($.isFunction(callback)) callback(cachedData);
    }
};

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
    if (options.cache) {
        //Here is our identifier for the cache. Maybe have a better, safer ID (it depends on the object string representation here) ?
        // on $.ajax call we could also set an ID in originalOptions
        var id = originalOptions.url+ JSON.stringify(originalOptions.data);
        options.cache = false;
        options.beforeSend = function () {
            if (!localCache.exist(id)) {
                jqXHR.promise().done(function (data, textStatus) {
                    localCache.set(id, data);
                });
            }
            return true;
        };

    }
});

$.ajaxTransport("+*", function (options, originalOptions, jqXHR, headers, completeCallback) {

    //same here, careful because options.url has already been through jQuery processing
    var id = originalOptions.url+ JSON.stringify(originalOptions.data);

    options.cache = false;

    if (localCache.exist(id)) {
        return {
            send: function (headers, completeCallback) {
                completeCallback(200, "OK", localCache.get(id));
            },
            abort: function () {
                /* abort code, nothing needed here I guess... */
            }
        };
    }
});

$(function () {
    var url = '/echo/jsonp/';
    $('#ajaxButton').click(function (e) {
        $.ajax({
            url: url,
            data: {
                test: 'value'
            },
            cache: true
        }).done(function (data, status, jq) {
            console.debug({
                data: data,
                status: status,
                jqXHR: jq
            });
        });
    });
});

Fiddle HERE Niektóre problemy, nasz identyfikator pamięci podręcznej jest zależny od reprezentacji obiektu JSON json2 lib.

Użyj widoku konsoli (F12) lub FireBug, aby wyświetlić niektóre dzienniki wygenerowane przez pamięć podręczną.

TecHunter
źródło
czy istnieje powód, dla którego umieszczasz wywołanie zwrotne w localCache.setfunkcji? Dlaczego nie doSomehing(jqXHR)po prostu po ustawieniu pamięci podręcznej?
cammil
Po prostu wolę to w ten sposób, więc nie muszę robić czegoś takiego, doSomething(localCache.set(url,jqXHR));ale to tylko osobiste preferencje
TecHunter
2
Jakieś sugestie dotyczące ulepszenia tego, aby wspierać używanie $ .ajax jako obietnicy? Zwrócenie wartości false z beforeSend anuluje żądanie (tak jak powinno), więc istniejąca $ .ajax (...). Done (function (response) {...}). Fail (...) teraz przestaje działać, ponieważ wywoływana jest funkcja fail niż zrobione ... i wolałbym nie przepisywać ich wszystkich :(
franck102
2
@TecHunter Bardzo dziękuję za to. Można by wprowadzić jeszcze trzy drobne ulepszenia. Po pierwsze, jeśli wiele żądań jest kierowanych do tego samego zasobu w tym samym czasie, wszystkie pominą pamięć podręczną. Aby to naprawić, możesz chcieć ustawić pamięć podręczną dla określonego „identyfikatora” jako oczekującą przy pierwszym żądaniu i odroczyć wysyłanie wyników dla kolejnych żądań do czasu powrotu pierwszego. Po drugie, możesz chcieć buforować wynik błędu żądania, aby wszystkie żądania dotyczące tego samego zasobu miały ten sam wynik.
mjhm
2
@TecHunter - Niezłe rozwiązanie, zastosowałem to rozwiązanie z jedną ważną zmianą. Jeśli obiekt w pamięci podręcznej jest modyfikowany w innych funkcjach, spowoduje to problem, więc podczas ustawiania i pobierania obiektu w pamięci podręcznej zwracam kopię tego obiektu, która jak pokazano poniżej: localCache.data[url] = { _: new Date().getTime(), data: _.cloneDeep(cachedData, true) }; _.cloneDeep(localCache.data[url].data, true)
Bharat Patil
12

Szukałem pamięci podręcznej do przechowywania aplikacji Phonegap i znalazłem odpowiedź @TecHunter, która jest świetna, ale zrobiona przy użyciu localCache.

Znalazłem i dowiedziałem się, że localStorage to kolejna alternatywa dla buforowania danych zwracanych przez wywołanie ajax. Dlatego stworzyłem jedno demo, localStoragektóre pomoże innym, którzy mogą chcieć używać localStoragezamiast localCachebuforowania.

Połączenie Ajax:

$.ajax({
    type: "POST",
    dataType: 'json',
    contentType: "application/json; charset=utf-8",
    url: url,
    data: '{"Id":"' + Id + '"}',
    cache: true, //It must "true" if you want to cache else "false"
    //async: false,
    success: function (data) {
        var resData = JSON.parse(data);
        var Info = resData.Info;
        if (Info) {
            customerName = Info.FirstName;
        }
    },
    error: function (xhr, textStatus, error) {
        alert("Error Happened!");
    }
});

Aby przechowywać dane w localStorage:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if (options.cache) {
    var success = originalOptions.success || $.noop,
        url = originalOptions.url;

    options.cache = false; //remove jQuery cache as we have our own localStorage
    options.beforeSend = function () {
        if (localStorage.getItem(url)) {
            success(localStorage.getItem(url));
            return false;
        }
        return true;
    };
    options.success = function (data, textStatus) {
        var responseData = JSON.stringify(data.responseJSON);
        localStorage.setItem(url, responseData);
        if ($.isFunction(success)) success(responseJSON); //call back to original ajax call
    };
}
});

Jeśli chcesz usunąć localStorage, użyj następującej instrukcji w dowolnym miejscu:

localStorage.removeItem("Info");

Mam nadzieję, że pomoże to innym!

immayankmodi
źródło
Cześć, za localStorage.removeItem("Info");, czy to "info"jest adres URL?
vsogrimen
@vsogrimen infojest obiektem do przechowywania danych w localStorage.
immayankmodi
1
dalej się responseJSON is not defined. Jakiś sposób to naprawić? (mój typ danych to html)
Nikita 웃
@CreativeMind, usuń reponseJOSN i użyj "var responseData = JSON.stringify (data);" zamiast tego zrób to samo z sukcesem (dane)
Unicco
Wolę localStorage, ponieważ potrzebowałem pamięci podręcznej dla wielu żądań.
KeitelDOG
9

Wszystkie nowoczesne przeglądarki zapewniają pamięć masową API. Możesz ich użyć (localStorage lub sessionStorage), aby zapisać swoje dane.

Wystarczy, że po otrzymaniu odpowiedzi zapiszesz ją w pamięci przeglądarki. Następnie następnym razem, gdy znajdziesz to samo połączenie, wyszukaj, czy odpowiedź została już zapisana. Jeśli tak, zwróć stamtąd odpowiedź; jeśli nie, wykonaj nowe połączenie.

Wtyczka Smartjax również robi podobne rzeczy; ale ponieważ Twoim wymaganiem jest po prostu zapisanie odpowiedzi na wywołanie, możesz napisać swój kod wewnątrz funkcji sukcesu jQuery ajax, aby zapisać odpowiedź. A przed wykonaniem połączenia sprawdź, czy odpowiedź jest już zapisana.

Paul Shan
źródło
Mam odpowiedzi zapisane w IndexedDB, czy istnieje sposób sprawdzenia IndexedDB? Ponadto, jeśli używam Session Storage, to jest sposób, aby sprawdzić, czy odpowiedź jest obecna, czy nie, używając jQuery. Nie mogę dołączyć żadnej innej biblioteki niż jQuery. Dzięki,
Abhishek Aggarwal
7

Jeśli zrozumiałem twoje pytanie, oto rozwiązanie:

    $.ajaxSetup({ cache: true});

i dla konkretnych wezwań

 $.ajax({
        url: ...,
        type: "GET",
        cache: false,           
        ...
    });

Jeśli chcesz odwrotnie (pamięć podręczna dla określonych połączeń), możesz ustawić false na początku i true dla określonych połączeń.

wiosna
źródło
2
to jest dokładnie odwrotnie
TecHunter
U mnie działa, dziękuję! To dziwne, że buforowanie nie było domyślnie włączone na mojej stronie.
Timur Nurlygayanov
2

Stare pytanie, ale moje rozwiązanie jest nieco inne.

Pisałem pojedynczą stronę internetową, która nieustannie wykonywała wywołania Ajax wyzwalane przez użytkownika, a żeby było jeszcze trudniej, wymagało to bibliotek korzystających z metod innych niż jquery (takich jak dojo, natywne xhr itp.). Napisałem wtyczkę do jednej z moich własnych bibliotek aby buforować żądania Ajax tak wydajnie, jak to tylko możliwe, w sposób, który działałby we wszystkich głównych przeglądarkach, niezależnie od tego, które biblioteki były używane do wykonywania wywołania Ajax.

Rozwiązanie wykorzystuje jSQL (napisane przeze mnie - trwałą implementację SQL po stronie klienta napisane w javascript, która używa indexeddb i innych metod przechowywania danych) i jest dołączone do innej biblioteki o nazwie XHRCreep (napisanej przeze mnie), która jest całkowitym przepisaniem natywny obiekt XHR.

Aby zaimplementować wszystko, co musisz zrobić, umieść wtyczkę na swojej stronie, która jest tutaj .

Istnieją dwie możliwości:

jSQL.xhrCache.max_time = 60;

Ustaw maksymalny wiek w minutach. wszelkie starsze odpowiedzi z pamięci podręcznej są ponownie żądane. Wartość domyślna to 1 godzina.

jSQL.xhrCache.logging = true;

Po ustawieniu na true w konsoli zostaną pokazane pozorowane wywołania XHR do debugowania.

Możesz wyczyścić pamięć podręczną na dowolnej stronie za pośrednictwem

jSQL.tables = {}; jSQL.persist();
Raz siłowałem się z niedźwiedziem.
źródło
Nie mogę znaleźć twojej wtyczki na githubie i na oficjalnej stronie. Zaktualizuj swoją odpowiedź Potrzebuję twojej wtyczki :) Jestem obserwatorem na githubie. Dobra robota;) @ occams-razor
Alican Kablan
-1
        function getDatas() {
            let cacheKey = 'memories';

            if (cacheKey in localStorage) {
                let datas = JSON.parse(localStorage.getItem(cacheKey));

                // if expired
                if (datas['expires'] < Date.now()) {
                    localStorage.removeItem(cacheKey);

                    getDatas()
                } else {
                    setDatas(datas);
                }
            } else {
                $.ajax({
                    "dataType": "json",
                    "success": function(datas, textStatus, jqXHR) {
                        let today = new Date();

                        datas['expires'] = today.setDate(today.getDate() + 7) // expires in next 7 days

                        setDatas(datas);

                        localStorage.setItem(cacheKey, JSON.stringify(datas));
                    },
                    "url": "http://localhost/phunsanit/snippets/PHP/json.json_encode.php",
                });
            }
        }

        function setDatas(datas) {
            // display json as text
            $('#datasA').text(JSON.stringify(datas));

            // your code here
           ....

        }

        // call
        getDatas();

wprowadź opis linku tutaj

user1634982
źródło