Almacenamiento en caché de una respuesta jquery ajax en javascript / browser

96

Me gustaría habilitar el almacenamiento en caché de una respuesta ajax en javascript / browser.

De los documentos de jquery.ajax :

De forma predeterminada, las solicitudes siempre se emiten, pero el navegador puede ofrecer resultados desde su caché. Para no permitir el uso de los resultados almacenados en caché, establezca la caché en falso. Para hacer que la solicitud informe un error si el activo no se ha modificado desde la última solicitud, establezca ifModified en verdadero.

Sin embargo, ninguno de estos aborda el almacenamiento en caché forzado.

Motivación: quiero poner $.ajax({...})llamadas en mis funciones de inicialización, algunas de las cuales solicitan la misma URL. A veces necesito llamar a una de estas funciones de inicialización, a veces llamo a varias.

Entonces, quiero minimizar las solicitudes al servidor si esa URL en particular ya se ha cargado.

Podría lanzar mi propia solución (¡con cierta dificultad!), Pero me gustaría saber si hay una forma estándar de hacerlo.

cammil
fuente
No habría pensado que sería difícil rastrear qué URL ya ha cargado y almacenar los resultados en esa lista. Luego, puede verificar sus URL antes de realizar una llamada AJAX. Voila, tienes tu propio caché básico.
puede agregar el encabezado de control de caché y expira a su respuesta en el servidor, por lo que su servidor solo debe ser llamado después del tiempo de espera que configuró en esos valores
aquí

Respuestas:

141

cache:true solo funciona con solicitudes GET y HEAD.

Podría desarrollar su propia solución como dijo con algo como esto:

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

Trabajando el violín aquí

EDITAR: a medida que esta publicación se vuelve popular, aquí hay una respuesta aún mejor para aquellos que desean administrar el caché de tiempo de espera y tampoco tienen que preocuparse por todo el desorden en $ .ajax () ya que yo uso $ .ajaxPrefilter () . Ahora solo la configuración {cache: true}es suficiente para manejar el caché correctamente:

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

Y el violín aquí CUIDADOSO, no funciona con $ .Deferred

Aquí hay una implementación funcional pero defectuosa que funciona con diferido:

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

Violín AQUÍ Algunos problemas, nuestro ID de caché depende de la representación del objeto JSON lib json2.

Use la vista Consola (F12) o FireBug para ver algunos registros generados por la caché.

TecHunter
fuente
¿Hay alguna razón por la que pones una devolución de llamada en la localCache.setfunción? ¿Por qué no simplemente doSomehing(jqXHR)después de configurar el caché?
cammil
Simplemente lo prefiero de esta manera para no tener que hacer algo como, doSomething(localCache.set(url,jqXHR));pero es solo una preferencia personal
TecHunter
2
¿Alguna sugerencia para mejorar esto para admitir el uso de $ .ajax como promesa? Devolver false de beforeSend cancela la solicitud (como debería) por lo que $ .ajax (...). Done (function (response) {...}). Fail (...) ahora deja de funcionar porque se invoca a fail que hecho ... y preferiría no reescribirlos todos :(
franck102
2
@TecHunter Muchas gracias por esto. Se podrían realizar tres pequeñas mejoras más. Primero, si se realizan varias solicitudes al mismo recurso al mismo tiempo, todas perderán el caché. Para solucionar esto, es posible que desee configurar la caché para un determinado "id" como pendiente con la primera solicitud y diferir el envío de resultados para solicitudes posteriores hasta que vuelva la primera. En segundo lugar, es posible que desee almacenar en caché el resultado del error de una solicitud para que todas las solicitudes del mismo recurso obtengan el mismo resultado.
mjhm
2
@TecHunter - Buena solución, utilicé esta solución con un cambio importante. Si el objeto almacenado en caché se está modificando en otras funciones, causará problemas, por lo que mientras configura y obtengo el objeto almacenado en caché, estoy devolviendo una copia de ese objeto que se muestra a continuación: localCache.data[url] = { _: new Date().getTime(), data: _.cloneDeep(cachedData, true) }; _.cloneDeep(localCache.data[url].data, true)
Bharat Patil
11

Estaba buscando almacenamiento en caché para el almacenamiento de mi aplicación phonegap y encontré la respuesta de @TecHunter, que es genial, pero ya he terminado de usar localCache .

Encontré y llegué a saber que localStorage es otra alternativa para almacenar en caché los datos devueltos por la llamada ajax. Entonces, creé una demostración usando localStorageque ayudará a otros que quieran usar en localStoragelugar delocalCache almacenar en caché.

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

Para almacenar datos en 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
    };
}
});

Si desea eliminar localStorage, use la siguiente declaración donde quiera:

localStorage.removeItem("Info");

¡Espero que ayude a otros!

immayankmodi
fuente
Hola, en localStorage.removeItem("Info");, ¿ "info"es la URL?
vsogrimen
@vsogrimen infoes el objeto para almacenar los datos en localStorage.
immayankmodi
1
sigue recibiendo responseJSON is not defined. Cualquier forma de arreglar esto? (mi tipo de datos es html)
Nikita 웃
@CreativeMind, elimine reponseJOSN y use "var responseData = JSON.stringify (data);" en su lugar, haga lo mismo con el éxito (datos)
Unicco
Prefiero el localStorage ya que necesitaba caché en múltiples solicitudes.
KeitelDOG
9

Todos los navegadores modernos le proporcionan apis de almacenamiento. Puede usarlos (localStorage o sessionStorage) para guardar sus datos.

Todo lo que tiene que hacer es después de recibir la respuesta, almacenarla en el almacenamiento del navegador. Luego, la próxima vez que encuentre la misma llamada, busque si la respuesta ya está guardada. Si es así, devuelva la respuesta desde allí; si no, haga una nueva llamada.

El complemento Smartjax también hace cosas similares; pero como su requisito es simplemente guardar la respuesta a la llamada, puede escribir su código dentro de su función de éxito jQuery ajax para guardar la respuesta. Y antes de realizar la llamada, compruebe si la respuesta ya está guardada.

Paul Shan
fuente
Tengo respuestas guardadas en IndexedDB, ¿hay alguna forma de verificar IndexedDB? Además, si uso Session Storage, ¿hay alguna manera de verificar si la respuesta está presente o no usando jQuery? No puedo incluir ninguna biblioteca que no sea jQuery. Gracias,
Abhishek Aggarwal
7

Si entendí tu pregunta, aquí está la solución:

    $.ajaxSetup({ cache: true});

y para llamadas puntuales

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

Si desea lo contrario (caché para llamadas específicas), puede establecer falso al principio y verdadero para llamadas específicas.

primavera
fuente
2
eso es exactamente lo contrario
TecHunter
¡Funciona para mí, gracias! Es extraño que el almacenamiento en caché no estuviera habilitado de forma predeterminada en mi página ..
Timur Nurlygayanov
2

Antigua pregunta, pero mi solución es un poco diferente.

Estaba escribiendo una aplicación web de una sola página que constantemente hacía llamadas ajax activadas por el usuario, y para hacerlo aún más difícil, requería bibliotecas que usaran métodos distintos de jquery (como dojo, xhr nativo, etc.). Escribí un complemento para una de mis propias bibliotecas para almacenar en caché las solicitudes ajax de la manera más eficiente posible de una manera que funcionaría en todos los navegadores principales, independientemente de las bibliotecas que se estuvieran utilizando para realizar la llamada ajax.

La solución usa jSQL (escrito por mí, una implementación de SQL persistente del lado del cliente escrita en javascript que usa indexeddb y otros métodos de almacenamiento dom), y está incluida con otra biblioteca llamada XHRCreep (escrita por mí) que es una reescritura completa de el objeto XHR nativo.

Para implementar todo lo que necesita hacer es incluir el complemento en su página, que está aquí .

Hay dos opciones:

jSQL.xhrCache.max_time = 60;

Establezca la edad máxima en minutos. Se vuelven a solicitar las respuestas almacenadas en caché que sean más antiguas. El valor predeterminado es 1 hora.

jSQL.xhrCache.logging = true;

Cuando se establece en verdadero, las llamadas XHR simuladas se mostrarán en la consola para su depuración.

Puede borrar el caché en cualquier página a través de

jSQL.tables = {}; jSQL.persist();
Una vez luché con un oso.
fuente
No puedo encontrar tu complemento en github ni en el sitio web oficial. Actualiza tu respuesta Necesito tu complemento :) Soy seguidor tuyo en github. Buen trabajo;) @ 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();

ingrese la descripción del enlace aquí

usuario1634982
fuente