【问题标题】:AJAX call caching via Promise makes browser use CPU (and leak memory)通过 Promise 的 AJAX 调用缓存使浏览器使用 CPU(并泄漏内存)
【发布时间】:2016-02-09 01:09:00
【问题描述】:

更新:在长时间的 jslint 会话之后,问题神秘地消失了,所以我必须得出结论,这是我的代码中某处的一些愚蠢错误,通过删除一些看似无害的错字得到了修复。 p>

最初的问题

我有一个页面通过 jQuery .post 调用加载大量(随机变量)AJAX 对象。

所以我会产生很多电话,例如

/entry/get/orange
/entry/get/red
/entry/get/yellow
/entry/get/red      <--- duplicate!
/entry/get/red      <--- again!
/entry/get/yellow
/entry/get/red      <--- dude, what's wrong with you?
...

当以这种方式运行时,一切都按其应有的方式运行,除了它非常非常低效,并且非常不必要地访问服务器。

解决方案尝试

所以我通过 Promise 切换到缓存,如下所示:

// I have a global colorCache variable

function retrieve(color, callback) {
    console.log("retrieving " + color);
    var found = -1;
    // Each entry in colorCache is an object with some other values
    for (var i in colorCache) {
        if (colorCache[i].color === color) {
             found = i;
             break;
        }
    }
    if (found < 0) {
        console.log("effecting call for " + color);
        var url  = "blah/blah/blah/" + color;
        var ppar = {
            foo: "bar",
            answer: 42
        };
        /* push returns the new length of colorCache */
        found = colorCache.push({ 
            color   : color,
            promise : $.post(url, ppar),
            ts      : new Date().getTime()
        }) - 1;
    } else {
        console.log("cache hit for " + color);
    }
    // now found is valid. Return the Promise too, for appending.
    return colorCache[found].success(callback);
}

这样,如果相关的.post 已经发出,则使用相同的数据再次调用回调,无论调用本身已经终止还是仍在进行中。

再次,一切正常。 显然

现在我有另一个问题

有了这个新版本,

  • 页面加载
  • 所有对象都已加载(比以前快得多 - 正如预期的那样)
  • 页面停止,一切都静止了……没有动画,没有时钟,什么都没有……
  • ...但 Process Explorer 现在显示 Firefox 不断占用 26-30% 的 CPU。它会继续这样做,直到并且除非我关闭加载页面的选项卡。此外,Firefox 内存消耗稳步上升。我看到它在不到一个小时的时间里从 800M 上升到 1.7GB,不是完全有规律的,而是有明显的上升趋势。

这可能是什么问题?

重要提示:Stack Overflow 上有几个“浏览器泄漏”问题和答案,但它们都涉及到分配对象或以某种方式迭代的重复 jQuery 调用。在这里,一旦我运行了大约 50 次 jQuery 调用,就没有其他事情发生了。

我尝试过的(到目前为止)

  • 我使用 Firebug 进行了检查:没有控制台警告或错误,没有迹象日志(我调用的所有函数都有一个),没有网络活动(从 Apache 日志服务器端仔细检查)。
  • 我查看了内存中的 Firefox 进程;主线程正在运行,但我不擅长解释 Process Explorer 的线程信息 - 它显示了一些关于“forInIterator”的东西,它告诉我的信息很少。
  • 似乎除了 CPU 外没有任何活动(网络、Windows 注册表读/写)。
  • 我搜索了很多,发现something 可能相关(但可能不相关)。 我还没有尝试过这个建议,但我会尽快尝试,即使我看不到它有什么帮助,而且无论如何我不会使用when.js,即使有类似的东西可能已经在 jQuery 中蔓延,因为:一个潜在的解决方法是确保 你的承诺回调和 errbacks 返回一些东西(除了 $.Deferred)。
  • 按照 Jaromanda X 的建议使用 Promise.resolve 包装 .post 调用,并将缓存对象移动到函数内以避免污染全局范围(这始终是一个好习惯)。
  • 直接返回Promise对象,并通过附加外部.then调用回调。
  • 用 Chrome 测试。结果相同(在某些方面令人放心),但 CPU 负载恒定为 17%。
  • 关闭选项卡可以减轻 CPU 负载。

【问题讨论】:

  • 你不需要它是:return colorCache[found].promise.then(callback);colorCache[found] 是您的对象,它没有您尝试调用的 .success() 方法。另外,我切换到.then() 是为了更好地兼容未来,因为.success() 是非标准的。
  • 我已经阅读了 jQuery.deferred 不是 Promise/A+ 规范 - 这并不意味着这是这个问题的根源,但是,尝试“包装” $.post(url, ppar) 在本机 Promise.resolve($.post(url, ppar)) 中,看看 cpu 使用问题是否消失 - 另外,这是您的检索函数的 fiddle 不需要全局变量,并简化(我认为)逻辑(没有 for 循环例如,用于查找缓存值)-小提琴包装了本评论中提到的 jQuery
  • @JaromandaX ,我试过了,但没有用。我现在将尝试减少调用次数,看看是否能摆脱一些线索。
  • “我有一个页面通过 jQuery .post 调用加载大量(随机变量)AJAX 对象。” - 重新考虑您的架构可能是个好主意 - 我们不会为您提供任何解决方案 - 这是一个架构问题。
  • 是否可以选择使用不蹩脚的 promise 库或 request 库?

标签: jquery ajax firefox promise


【解决方案1】:

TL;DR问题与缓存完全没有直接相关。缓存,或者更确切地说:缓存允许的更高的调用周转时间,似乎在 jQuery/bootstrapSwitch 关系的其他地方引发了误解。也有可能是我错误地使用了 bootstrapSwitch。


调试和发现

由于没有运行 XHR 请求,CPU 问题肯定是由代码循环显式或隐式循环引起的(例如 setInterval)。

检测浏览器定时器功能

window.timers = {}; 
originalSetTimeout = window.setTimeout;
originalSetInterval = window.setInterval;
window.setTimeout = function(fu, t) {
    var id = originalSetTimeout(function() {
        console.log(id+" has timed out");
        delete window.timers[id]; // do not track popped timers 
        fu();
    }, t);
    window.timers[id] = {id:id, type: 'timeout', setAt: new Date(),  timeout: t };
    console.log(id+" has been set to pop in "+t+"ms");
}

window.setInterval = function(fu, t) {
    var id = originalSetInterval(function() {
        window.timers[id].fired++;
        fu();
    }, t);
    window.timers[id] = {id:id, type: 'interval', setAt: new Date(),  timeout: t, fired: 0 };
    console.log(id+" has been set to pop every "+t+"ms");
}

表示有几个计时器在连续运行

调查计时器

代码是“严格”的,但是一旦知道具体的计时器,我就可以在我的 setInterval 中抛出一个异常,并能够检查调用堆栈。

所以原来罪魁祸首是这段代码(删除它会使问题消失):

    .bootstrapSwitch({
        onSwitchChange: function(e, state) {
            ...
        }
    })

结论

问题的原因是我在结果页面中有很多复选框:它们被 bootstrapSwitch 美化了,但它们一开始是隐藏。在这种情况下,bootstrapSwitch 会启动一个计时器,等待复选框在丰富其 DOM 之前呈现。显然这是(其中一个?)执行此操作的函数:

 return initInterval = window.setInterval((function(_this) {
      return function() {
        if (_this.$wrapper.is(":visible")) {
          init();
          return window.clearInterval(initInterval);
        }
      };
    })(this), 50);

通常这些函数不会显着增加 CPU 负载。但由于不明原因,短时间内大量(40+)的 AJAX 请求,其中一些调用了 bootstrapSwitch,使得上述定时器产生了更高的负载。

强制复选框开关一个接一个地显示,然后立即隐藏它们,这也有点工作:保留两个计时器,CPU 负载低于 5%,但所有其他计时器正确终止。不过,复选框闪烁很尴尬。

最后,我编写了一个计时器函数,它将延迟 bootstrapSwitch,直到相关的复选框 确实可见。一旦它们被调用,就会调用 bootstrapSwitch,但由于它们是可见的,因此不会创建额外的计时器。这样,计时器代码内部发生的任何事情都不会被触发。

【讨论】:

    猜你喜欢
    • 2011-03-08
    • 1970-01-01
    • 1970-01-01
    • 2020-04-04
    • 2023-03-27
    • 2012-01-13
    • 2011-09-22
    • 1970-01-01
    • 2021-01-13
    相关资源
    最近更新 更多