为什么通过XHR的异步onreadystatechange事件进行递归会消耗堆栈?

发布于 2024-12-09 10:23:46 字数 1544 浏览 0 评论 0原文

我在某些(但不是全部)IE7 计算机上遇到堆栈溢出错误。

该函数下载一堆基于 URL 的资源,但对它们不执行任何操作。它在我的登录页面上运行,其目的是在您输入凭据时获取静态内容,以便当您真正需要它时,浏览器可以从本地缓存中获取它。

// Takes an array of resources URLs and preloads them sequentially,
// but asynchronously, using an XHR object.
function preloadResources(resources) {

    // Kick it all off.
    if (resources.length > 0) {
        var xhr = getXHRObject(); // Prepare the XHR object which will be reused for each request.
        xhr.open('GET', resources.shift(), true);
        xhr.onreadystatechange = handleReadyStateChange;
        xhr.send(null);
    }

    // Handler for the XHR's onreadystatechange event.  Loads the next resource, if any.
    function handleReadyStateChange() {
        if (xhr.readyState == 4) {
            if (resources.length > 0) {
                xhr.open('GET', resources.shift(), true);
                xhr.onreadystatechange = arguments.callee;
                xhr.send(null);
            }
        }
    }

    // A safe cross-browser way to get an XHR object.
    function getXHRObject() {
        // Clipped for clarity.
    }

} // End preloadResources().

它的调用方式如下:

preloadResources([
    'http://example.com/big-image.png',
    'http://example.com/big-stylesheet.css',
    'http://example.com/big-script.js']);

它递归地处理 URL 数组。我认为它不易受到堆栈溢出错误的影响,因为每个递归都是从异步事件调用的 - XHR 的 onreadystatechange 事件(请注意,我正在调用 xhr.open()异步)。我觉得这样做会阻止它增加堆栈。

我不明白堆栈是如何增长失控的? 我哪里出错了?

I'm getting a stack overflow error on some, but not all, IE7 machines.

This function downloads a bunch URL-based resources and does nothing with them. It runs on my login page and its purpose is to fetch static content while your typing in your credentials, so that when you really need it, the browser can get it from its local cache.

// Takes an array of resources URLs and preloads them sequentially,
// but asynchronously, using an XHR object.
function preloadResources(resources) {

    // Kick it all off.
    if (resources.length > 0) {
        var xhr = getXHRObject(); // Prepare the XHR object which will be reused for each request.
        xhr.open('GET', resources.shift(), true);
        xhr.onreadystatechange = handleReadyStateChange;
        xhr.send(null);
    }

    // Handler for the XHR's onreadystatechange event.  Loads the next resource, if any.
    function handleReadyStateChange() {
        if (xhr.readyState == 4) {
            if (resources.length > 0) {
                xhr.open('GET', resources.shift(), true);
                xhr.onreadystatechange = arguments.callee;
                xhr.send(null);
            }
        }
    }

    // A safe cross-browser way to get an XHR object.
    function getXHRObject() {
        // Clipped for clarity.
    }

} // End preloadResources().

It's called like this:

preloadResources([
    'http://example.com/big-image.png',
    'http://example.com/big-stylesheet.css',
    'http://example.com/big-script.js']);

It recursively processes an array of URLs. I thought it wasn't susceptible to stack overflow errors because each recursion is called from an asynchronous event -- the XHR's onreadystatechange event (notice that I'm calling xhr.open() asynchronously). I felt doing so would prevent it from growing the stack.

I don't see how the stack is growing out of control? Where did I go wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

天赋异禀 2024-12-16 10:23:46

使用计时器进行递归可以防止堆栈溢出问题的出现。

// Handler for the XHR's onreadystatechange event.  Loads the next resource, if any.
function handleReadyStateChange() {
    if (xhr.readyState == 4 && resources.length > 0) {
        setTimeout(function() {
            xhr.open('GET', resources.shift(), true);
            xhr.onreadystatechange = handleReadyStateChange;
            xhr.send(null);
        }, 10);
    }
}

我猜想将 XHR 请求链接到另一个请求会消耗堆栈。将它们与计时器链接在一起可以防止这种情况发生——至少在 IE7 中是这样。我没有在其他浏览器上看到这个问题,所以我无法判断。

Doing the recursion with a timer prevented the stack overflow problem from appearing.

// Handler for the XHR's onreadystatechange event.  Loads the next resource, if any.
function handleReadyStateChange() {
    if (xhr.readyState == 4 && resources.length > 0) {
        setTimeout(function() {
            xhr.open('GET', resources.shift(), true);
            xhr.onreadystatechange = handleReadyStateChange;
            xhr.send(null);
        }, 10);
    }
}

I guess chaining XHR requests to one another consumes the stack. Chaining them together with a timer prevents that -- at least in IE7. I haven't seen the problem on other browsers, so I can't tell.

对你再特殊 2024-12-16 10:23:46

您可以控制台记录或警告 arguments.callee 的值吗?我很好奇如果它解析为 preloadResources() 函数中的 arguments 变量而不是 handleReadyStateChange() 会发生什么。对我来说似乎不太可能,但只要我盯着你的代码,它就会让我感到惊讶。

然而,在回答您的问题时,我认为上面代码中的一个不好的做法是重用 XmlHttpRequest 对象,尤其是在没有让生命周期完成或调用 xhr.abort() 的情况下。这是我不久前隐藏起来的禁忌。这里以及网络上的各个地方都对此进行了讨论。请注意,IE 尤其不能很好地重用 xhr。请参阅http://ajaxian.com/archives/the-xmlhttprequest-reuse-dilemma .

希望这有帮助,

斯科特

Can you perhaps console log or alert the value of arguments.callee ? I'm curious what would happen if it resolves to the arguments variable from the preloadResources() function instead of handleReadyStateChange(). Doesn't seem likely to me, but it jumps out at me just eyeballing your code.

In answer to your question, however - I think one bad practice in the code above is reusing the XmlHttpRequest object, especially without ever letting the lifecycle complete or calling xhr.abort(). That's a no-no I tucked away a while back. It's discussed here and various places around the web. Note that IE particularly doesn't work well with reusing the xhr. See http://ajaxian.com/archives/the-xmlhttprequest-reuse-dilemma .

Hope this helps,

Scott

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文