Memorizzazione nella cache di una risposta jquery ajax in javascript / browser


96

Vorrei abilitare la memorizzazione nella cache di una risposta ajax in javascript / browser.

Dai documenti jquery.ajax :

Per impostazione predefinita, le richieste vengono sempre inviate, ma il browser può fornire risultati dalla sua cache. Per non consentire l'utilizzo dei risultati memorizzati nella cache, impostare la cache su false. Per causare la richiesta di segnalare un errore se l'asset non è stato modificato dall'ultima richiesta, impostare ifModified su true.

Tuttavia, nessuno di questi indirizzi impone la memorizzazione nella cache.

Motivazione: voglio inserire delle $.ajax({...})chiamate nelle mie funzioni di inizializzazione, alcune delle quali richiedono lo stesso URL. A volte ho bisogno di chiamare una di queste funzioni di inizializzazione, a volte ne chiamo diverse.

Quindi, voglio ridurre al minimo le richieste al server se quel particolare URL è già stato caricato.

Potrei lanciare la mia soluzione (con qualche difficoltà!), Ma vorrei sapere se esiste un modo standard per farlo.


Non avrei pensato che fosse difficile tenere traccia degli URL che hai già caricato e memorizzare i risultati in base a quell'elenco. Quindi, puoi controllare i tuoi URL prima di effettuare una chiamata AJAX. Voilà: hai la tua cache di base.

potresti aggiungere l'intestazione cache-control e expires alla tua risposta sul server, quindi il tuo server dovrebbe essere chiamato solo dopo il timeout che hai configurato in quei valori
hereandnow78

Risposte:


143

cache:true funziona solo con GET e HEAD request.

Potresti lanciare la tua soluzione come hai detto con qualcosa del genere:

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);
}

Violino funzionante qui

EDIT: man mano che questo post diventa popolare, ecco una risposta ancora migliore per coloro che vogliono gestire la cache di timeout e anche tu non devi preoccuparti di tutto il casino in $ .ajax () mentre io uso $ .ajaxPrefilter () . Ora basta l'impostazione {cache: true}per gestire correttamente la cache:

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);
}

E il violino qui ATTENZIONE, non funziona con $ .Deferred

Ecco un'implementazione funzionante ma imperfetta che funziona con differito:

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 QUI Alcuni problemi, il nostro ID cache dipende dalla rappresentazione dell'oggetto JSON di json2 lib.

Utilizza la vista Console (F12) o FireBug per visualizzare alcuni log generati dalla cache.


c'è un motivo per cui metti un callback sulla localCache.setfunzione? Perché non semplicemente doSomehing(jqXHR)dopo aver impostato la cache?
cammil

Preferisco solo in questo modo, quindi non devo fare qualcosa di simile, doSomething(localCache.set(url,jqXHR));ma è solo una preferenza personale
TecHunter

2
Qualche suggerimento per migliorare questo per supportare l'uso di $ .ajax come promessa? Restituendo false da beforeSend annulla la richiesta (come dovrebbe) quindi esistente $ .ajax (...). Done (function (response) {...}). Fail (...) ora smette di funzionare perché viene invocato piuttosto fail che fatto ... e preferirei non riscriverli tutti :(
franck102

2
@TecHunter Grazie mille per questo. Potrebbero essere apportati altri tre piccoli miglioramenti. Innanzitutto, se vengono effettuate più richieste contemporaneamente alla stessa risorsa, perderanno tutte la cache. Per risolvere questo problema potresti voler impostare la cache per un certo "id" in sospeso con la prima richiesta e rinviare l'invio dei risultati per le richieste successive fino al ritorno della prima. In secondo luogo, potresti voler memorizzare nella cache il risultato dell'errore di una richiesta in modo che tutte le richieste per la stessa risorsa ottengano lo stesso risultato.
mjhm

2
@TecHunter - Bella soluzione, ho usato questa soluzione con una modifica importante. Se l'oggetto memorizzato nella cache viene modificato in altre funzioni, causerà problemi, quindi durante l'impostazione e l'acquisizione di un oggetto memorizzato nella cache, restituisco una copia di quell'oggetto che, come mostrato di seguito: localCache.data[url] = { _: new Date().getTime(), data: _.cloneDeep(cachedData, true) }; _.cloneDeep(localCache.data[url].data, true)
Bharat Patil

12

Stavo cercando la memorizzazione nella cache per l'archiviazione della mia app phonegap e ho trovato la risposta di @TecHunter che è eccezionale ma è stata utilizzata localCache.

Ho scoperto e sono venuto a sapere che localStorage è un'altra alternativa per memorizzare nella cache i dati restituiti da una chiamata ajax. Quindi, ho creato una demo usando localStorageche aiuterà gli altri che potrebbero voler usare localStorageinvece che localCacheper il caching.

Chiamata 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!");
    }
});

Per memorizzare i dati in 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
    };
}
});

Se vuoi rimuovere localStorage, usa la seguente dichiarazione dove vuoi:

localStorage.removeItem("Info");

Spero che aiuti gli altri!


Ciao, a localStorage.removeItem("Info");proposito di "info"è l'URL?
vsogrimen

@vsogrimen infoè l'oggetto per memorizzare i dati in localStorage.
immayankmodi

1
continua a ricevere responseJSON is not defined. Qualche modo per risolvere questo problema? (il mio tipo di dati è html)
Nikita 웃

@CreativeMind, rimuovi reponseJOSN e usa "var responseData = JSON.stringify (data);" invece, fai lo stesso con successo (dati)
Unicco

Preferisco localStorage poiché avevo bisogno di cache su più richieste.
KeitelDOG

9

Tutti i browser moderni forniscono le API di archiviazione. Puoi usarli (localStorage o sessionStorage) per salvare i tuoi dati.

Tutto quello che devi fare è dopo aver ricevuto la risposta, salvala nella memoria del browser. Quindi la prossima volta che trovi la stessa chiamata, cerca se la risposta è già stata salvata. Se sì, restituisci la risposta da lì; se non fai una nuova chiamata.

Anche il plugin Smartjax fa cose simili; ma poiché la tua esigenza è solo salvare la risposta alla chiamata, puoi scrivere il tuo codice all'interno della funzione di successo jQuery ajax per salvare la risposta. E prima di effettuare la chiamata, controlla se la risposta è già stata salvata.


Ho delle risposte salvate in IndexedDB, c'è un modo per controllare IndexedDB? Inoltre, se utilizzo Session Storage, esiste un modo per verificare se la risposta è presente o meno utilizzando jQuery. Non posso includere alcuna libreria diversa da jQuery. Grazie,
Abhishek Aggarwal

7

Se ho capito la tua domanda, ecco la soluzione:

    $.ajaxSetup({ cache: true});

e per chiamate specifiche

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

Se vuoi opposto (cache per chiamate specifiche) puoi impostare false all'inizio e true per chiamate specifiche.


2
è esattamente l'opposto
TecHunter

Per me funziona, grazie! È strano che la memorizzazione nella cache non fosse abilitata di default sulla mia pagina ..
Timur Nurlygayanov

2

Vecchia domanda, ma la mia soluzione è un po 'diversa.

Stavo scrivendo un'app web a pagina singola che effettuava costantemente chiamate ajax attivate dall'utente e, per renderlo ancora più difficile, richiedeva librerie che usassero metodi diversi da jquery (come dojo, xhr nativo, ecc.). Ho scritto un plugin per una delle mie librerie per memorizzare nella cache le richieste ajax nel modo più efficiente possibile in un modo che avrebbe funzionato in tutti i principali browser, indipendentemente da quali librerie fossero utilizzate per effettuare la chiamata ajax.

La soluzione utilizza jSQL (scritta da me - un'implementazione SQL persistente lato client scritta in javascript che utilizza indexeddb e altri metodi di archiviazione dom) ed è fornita in bundle con un'altra libreria chiamata XHRCreep (scritta da me) che è una riscrittura completa di l'oggetto XHR nativo.

Per implementare tutto ciò che devi fare è includere il plugin nella tua pagina, che è qui .

Ci sono due opzioni:

jSQL.xhrCache.max_time = 60;

Imposta l'età massima in minuti. tutte le risposte memorizzate nella cache più vecchie di questa vengono nuovamente richieste. L'impostazione predefinita è 1 ora.

jSQL.xhrCache.logging = true;

Se impostato su true, le chiamate XHR fittizie verranno mostrate nella console per il debug.

Puoi svuotare la cache su una determinata pagina tramite

jSQL.tables = {}; jSQL.persist();

Non riesco a trovare il tuo plugin in GitHub e nel sito web ufficiale. Aggiorna la tua risposta Ho bisogno del tuo plugin :) Sono un tuo seguito in GitHub. Buon lavoro;) @ 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();

inserisci qui la descrizione del link

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.