Armazenando em cache uma resposta jquery ajax em javascript / navegador

96

Eu gostaria de habilitar o cache de uma resposta ajax em javascript / navegador.

Dos documentos jquery.ajax :

Por padrão, as solicitações são sempre emitidas, mas o navegador pode fornecer resultados fora de seu cache. Para não permitir o uso dos resultados armazenados em cache, defina cache como false. Para fazer a solicitação relatar falha se o ativo não tiver sido modificado desde a última solicitação, defina ifModified como true.

No entanto, nenhum desses endereços forçando o armazenamento em cache.

Motivação: Quero colocar $.ajax({...})chamadas nas minhas funções de inicialização, algumas das quais solicitam o mesmo url. Às vezes preciso chamar uma dessas funções de inicialização, às vezes chamo várias.

Portanto, quero minimizar as solicitações ao servidor se esse url específico já tiver sido carregado.

Eu poderia rolar minha própria solução (com alguma dificuldade!), Mas gostaria de saber se existe uma maneira padrão de fazer isso.

cammil
fonte
Eu não teria achado difícil rastrear quais URLs você já carregou e armazenar os resultados nessa lista. Então, você pode verificar seus URLs antes de fazer uma chamada AJAX. Voila - você tem seu próprio cache básico.
você poderia adicionar o cache-control e o cabeçalho de expiração à sua resposta no servidor, então seu servidor só deve ser chamado após o tempo limite que você configurou
nesses

Respostas:

143

cache:true só funciona com solicitação GET e HEAD.

Você poderia lançar sua própria solução, como disse algo ao longo destas linhas:

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

Trabalhando violino aqui

EDITAR: conforme este post se torna popular, aqui está uma resposta ainda melhor para quem deseja gerenciar o cache de timeout e você também não precisa se preocupar com toda a bagunça no $ .ajax () porque eu uso $ .ajaxPrefilter () . Agora, apenas configurar {cache: true}é suficiente para lidar com o cache corretamente:

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 o violino aqui CUIDADO, não trabalhando com $ .Deferred

Aqui está uma implementação funcional, mas falha, trabalhando com adiada:

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 AQUI Alguns problemas, nosso ID de cache é dependente da representação do objeto JSON json2 lib.

Use a visualização do console (F12) ou FireBug para visualizar alguns logs gerados pelo cache.

TecHunter
fonte
há um motivo para você colocar um retorno de chamada na localCache.setfunção? Por que não simplesmente doSomehing(jqXHR)após configurar o cache?
janeiro
Eu prefiro assim, então não tenho que fazer algo como, doSomething(localCache.set(url,jqXHR));mas é apenas preferência pessoal
TecHunter
2
Alguma sugestão para melhorar isso para suportar o uso de $ .ajax como uma promessa? Retornar false de beforeSend cancela a solicitação (como deveria), então existente $ .ajax (...). Done (function (response) {...}). Fail (...) agora para de funcionar porque fail é invocado do que feito ... e eu prefiro não reescrever todos eles :(
franck102
2
@TecHunter Muito obrigado por isso. Mais três pequenas melhorias poderiam ser feitas. Primeiro, se várias solicitações forem feitas ao mesmo recurso ao mesmo tempo, todas perderão o cache. Para corrigir isso, você pode definir o cache de um determinado "id" como pendente com a primeira solicitação e adiar o envio de resultados para solicitações subsequentes até que a primeira volte. Em segundo lugar, você pode querer armazenar em cache o resultado do erro de uma solicitação para que todas as solicitações do mesmo recurso obtenham o mesmo resultado.
mjhm
2
@TecHunter - Boa solução, usei essa solução com uma mudança importante. Se o objeto armazenado em cache estiver sendo modificado em outras funções, isso causará problemas, portanto, ao definir e obter o objeto armazenado em cache, estou retornando uma cópia desse objeto que, conforme mostrado abaixo: localCache.data[url] = { _: new Date().getTime(), data: _.cloneDeep(cachedData, true) }; _.cloneDeep(localCache.data[url].data, true)
Bharat Patil
12

Eu estava procurando por cache para o armazenamento do meu aplicativo phonegap e encontrei a resposta do @TecHunter que é ótimo, mas acabou de usar localCache.

Descobri e vim a saber que localStorage é outra alternativa para armazenar em cache os dados retornados por uma chamada ajax. Então, eu criei um demo usando o localStorageque ajudará outras pessoas que podem querer usar em localStoragevez de localCachecache.

Chamada 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 armazenar dados em 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 você deseja remover localStorage, use a seguinte instrução onde quiser:

localStorage.removeItem("Info");

Espero que ajude outras pessoas!

immayankmodi
fonte
Olá, em localStorage.removeItem("Info");, sobre o "info"é o url?
vsogrimen
@vsogrimen infoé o objeto para armazenar os dados em localStorage.
immayankmodi
1
continue recebendo responseJSON is not defined. Algum jeito de arrumar isso? (meu tipo de dados é html)
Nikita 웃
@CreativeMind, remova reponseJOSN e use "var responseData = JSON.stringify (data);" em vez disso, faça o mesmo com sucesso (dados)
Unicco
Eu prefiro o localStorage, pois precisava de cache em várias solicitações.
KeitelDOG
9

Todos os navegadores modernos fornecem apis de armazenamento. Você pode usá-los (localStorage ou sessionStorage) para salvar seus dados.

Tudo o que você precisa fazer é, após receber a resposta, armazená-la no armazenamento do navegador. Então, da próxima vez que você encontrar a mesma chamada, pesquise se a resposta já foi salva. Se sim, retorne a resposta de lá; se não fizer uma nova chamada.

O plugin Smartjax também faz coisas semelhantes; mas como seu requisito é apenas salvar a resposta da chamada, você pode escrever seu código dentro da função de sucesso do jQuery ajax para salvar a resposta. E antes de fazer a chamada é só verificar se a resposta já está salva.

Paul Shan
fonte
Tenho respostas salvas em IndexedDB. Existe uma maneira de verificar IndexedDB? Além disso, se eu usar o Armazenamento de Sessão, há uma maneira de verificar se a resposta está presente ou não usando jQuery. Não posso incluir nenhuma biblioteca diferente de jQuery. Obrigado,
Abhishek Aggarwal
7

Se entendi sua pergunta, aqui está a solução:

    $.ajaxSetup({ cache: true});

e para chamadas específicas

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

Se você quiser o oposto (cache para chamadas específicas), pode definir falso no início e verdadeiro para chamadas específicas.

Primavera
fonte
2
isso é exatamente o oposto
TecHunter
Funciona para mim, obrigado! É estranho que o cache não foi habilitado por padrão em minha página.
Timur Nurlygayanov
2

Pergunta antiga, mas minha solução é um pouco diferente.

Eu estava escrevendo um aplicativo da web de página única que constantemente fazia chamadas ajax acionadas pelo usuário e, para tornar ainda mais difícil, exigia bibliotecas que usavam métodos diferentes do jquery (como dojo, xhr nativo, etc). Eu escrevi um plugin para uma de minhas próprias bibliotecas para armazenar em cache as solicitações ajax da maneira mais eficiente possível, de uma forma que funcionasse em todos os principais navegadores, independentemente de quais bibliotecas estavam sendo usadas para fazer a chamada ajax.

A solução usa jSQL (escrito por mim - uma implementação de SQL persistente do lado do cliente escrita em javascript que usa indexeddb e outros métodos de armazenamento dom), e é empacotada com outra biblioteca chamada XHRCreep (escrita por mim) que é uma reescrita completa de o objeto XHR nativo.

Para implementar tudo que você precisa fazer é incluir o plugin em sua página, que está aqui .

Existem duas opções:

jSQL.xhrCache.max_time = 60;

Defina a idade máxima em minutos. quaisquer respostas em cache mais antigas do que isso são solicitadas novamente. O padrão é 1 hora.

jSQL.xhrCache.logging = true;

Quando definido como verdadeiro, as chamadas XHR simuladas serão mostradas no console para depuração.

Você pode limpar o cache em qualquer página via

jSQL.tables = {}; jSQL.persist();
Uma vez eu lutei com um urso.
fonte
Não consigo encontrar o seu plugin no github e no site oficial. Por favor, atualize sua resposta Eu preciso do seu plugin :) Sou seu seguidor no github. Bom trabalho;) @ 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();

insira a descrição do link aqui

user1634982
fonte