【问题标题】:Caching a jquery ajax response in javascript/browser在 javascript/浏览器中缓存 jquery ajax 响应
【发布时间】:2013-06-10 20:29:19
【问题描述】:

我想在 javascript/浏览器中启用 ajax 响应缓存。

来自jquery.ajax docs

默认情况下,总是发出请求,但浏览器可能会提供 结果超出其缓存。要禁止使用缓存的结果,请设置 缓存为假。如果资产导致请求报告失败 自上次请求后未修改,设置 ifModified 为 true。

但是,这些地址都不会强制缓存。

动机: 我想把$.ajax({...}) 调用放在我的初始化函数中,其中一些请求相同的url。有时我需要调用这些初始化函数之一,有时我需要调用几个。

所以,如果该特定 url 已经加载,我想尽量减少对服务器的请求。

我可以推出自己的解决方案(有一些困难!),但我想知道是否有标准的方法来做到这一点。

【问题讨论】:

  • 我认为跟踪您已加载的 URL 并将结果存储在该列表中并不困难。然后,您可以在进行 AJAX 调用之前检查您的 URL。瞧——你有自己的基本缓存。
  • 您可以将缓存控制和过期标头添加到服务器上的响应中,因此您的服务器只能在您在该值中配置的超时后调用
  • 它很长,但你能帮我理解为什么你需要在 ajax 请求中缓存(可能用外行的话)

标签: javascript jquery ajax caching browser


【解决方案1】:

cache:true 仅适用于 GET 和 HEAD 请求。

您可以按照您所说的那样推出自己的解决方案:

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

Working fiddle here

编辑:随着这篇文章变得流行,对于那些想要管理超时缓存的人来说,这里是一个更好的答案,而且你也不必为$.ajax() 中的所有混乱而烦恼,因为我使用$.ajaxPrefilter()。现在只需设置{cache: true} 即可正确处理缓存:

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

And the fiddle here 小心,不要使用 $.Deferred

这是一个使用 deferred 的有效但有缺陷的实现:

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 有些问题,我们的缓存 ID 依赖于 json2 lib JSON 对象表示。

使用控制台视图(F12)或FireBug查看缓存生成的一些日志。

【讨论】:

  • 您是否有理由在localCache.set 函数上设置回调?设置缓存后为什么不直接doSomehing(jqXHR)
  • 我只是更喜欢这种方式,所以我不必做类似doSomething(localCache.set(url,jqXHR)); 之类的事情,但这只是个人喜好
  • 有什么建议可以改进它以支持使用 $.ajax 作为承诺?从 beforeSend 返回 false 会取消请求(应该如此),因此现有的 $.ajax(...).done(function(response) {...}).fail(...) 现在停止工作,因为调用了失败而不是而不是完成......我宁愿不重写它们:(
  • @TecHunter 非常感谢。还可以进行三处轻微改进。首先,如果同时对同一个资源发出多个请求,它们都会错过缓存。要解决此问题,您可能希望将某个“id”的缓存设置为与第一个请求一起挂起,并将后续请求的发送结果推迟到第一个请求返回。其次,您可能希望缓存请求的错误结果,以便对同一资源的所有请求得到相同的结果。
  • @TecHunter - 不错的解决方案,我使用此解决方案进行了一项重要更改。如果缓存对象在其他函数中被修改,则会导致问题,因此在设置和获取缓存对象时,我将返回该对象的副本,如下所示:localCache.data[url] = { _: new Date().getTime(), data: _.cloneDeep(cachedData, true) }; _.cloneDeep(localCache.data[url].data, true)
【解决方案2】:

我正在为我的 phonegap 应用程序存储寻找缓存,我找到了 @TecHunter 的答案,这很好,但使用 localCache 完成。

我发现并知道 localStorage 是缓存 ajax 调用返回的数据的另一种选择。因此,我使用localStorage 创建了一个演示,这将有助于其他可能想要使用localStorage 而不是localCache 进行缓存的人。

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

将数据存储到 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
    };
}
});

如果要删除 localStorage,请在任意位置使用以下语句:

localStorage.removeItem("Info");

希望对他人有所帮助!

【讨论】:

  • 嗨,localStorage.removeItem("Info");,关于"info" 是网址吗?
  • @vsogrimen info 是在localStorage中存储数据的对象。
  • 继续获取responseJSON is not defined。有任何解决这个问题的方法吗? (我的数据类型是 html)
  • @CreativeMind,移除reponseJOSN,使用“var responseData = JSON.stringify(data);”相反,对成功(数据)做同样的事情
  • 我更喜欢 localStorage,因为我需要跨多个请求进行缓存。
【解决方案3】:

所有现代浏览器都为您提供存储 API。您可以使用它们(localStorage 或 sessionStorage)来保存您的数据。

您所要做的就是在收到响应后将其存储到浏览器存储中。然后下次找到相同的呼叫时,搜索是否已保存响应。如果是,则从那里返回响应;如果不打新电话。

Smartjax插件也做类似的事情;但由于您的要求只是保存调用响应,您可以在 jQuery ajax 成功函数中编写代码来保存响应。在拨打电话之前,只需检查响应是否已保存。

【讨论】:

  • 我在 IndexedDB 中保存了响应,有没有办法检查 IndexedDB?此外,如果我使用会话存储,那么有没有办法使用 jQuery 检查响应是否存在。我不能包含除 jQuery 之外的任何库。谢谢,
【解决方案4】:

如果我理解你的问题,这里是解决方案:

    $.ajaxSetup({ cache: true});

对于特定的调用

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

如果你想要相反(特定调用的缓存),你可以在开头设置 false 并为特定调用设置 true。

【讨论】:

  • 恰恰相反
  • 它对我有用,谢谢!奇怪的是我的页面默认没有启用缓存..
  • 缓存多长时间?
【解决方案5】:

老问题,但我的解决方案有点不同。

我正在编写一个单页 Web 应用程序,该应用程序不断地进行由用户触发的 ajax 调用,为了使它变得更加困难,它需要使用 jquery 以外的方法的库(如 dojo、本机 xhr 等)。我为 my own libraries 之一编写了一个插件,以尽可能高效地缓存 ajax 请求,这种方式适用于所有主流浏览器,无论使用哪个库进行 ajax 调用。

该解决方案使用jSQL(由我编写 - 用 javascript 编写的客户端持久 SQL 实现,使用 indexeddb 和其他 dom 存储方法),并与另一个名为 XHRCreep 的库捆绑在一起(由我编写)是对原生 XHR 对象的完全重写。

要实现所有你需要做的就是在你的页面中包含插件,which is here

有两种选择:

jSQL.xhrCache.max_time = 60;

以分钟为单位设置最长期限。任何比这更早的缓存响应都会被重新请求。默认为 1 小时。

jSQL.xhrCache.logging = true;

设置为 true 时,模拟 XHR 调用将显示在控制台中以进行调试。

您可以通过

清除任何给定页面上的缓存
jSQL.tables = {}; jSQL.persist();

【讨论】:

  • 我在 github 和官方网站上找不到你的插件。请更新你的答案我需要你的插件 :) 我是你在 github 的追随者。干得好;)@occams-razor
【解决方案6】:
        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();

enter link description here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-16
    • 2017-06-13
    • 2011-04-23
    • 2014-03-08
    • 1970-01-01
    • 2018-05-08
    相关资源
    最近更新 更多