asimes
asimes

Reputation: 5904

XMLHttpRequest and effects of scope

Bounty Note: Simple question is, why don't I need to worry about goGet being removed before the request finished if it is an asynchronous request?

I have a form that is generated with PHP which produces many HTML rows I'll refer to as "Entries". Each Entry has two options, "Delete" and "Edit", each of which may need to create zero or more XMLHttpRequests.

To try to make handling requests manageable, I made a XmlHttpRequest class, shown below:

function XmlHttpRequest(inElement) {
    this.request = new XMLHttpRequest();
    this.write = inElement;

    var self = this;
    this.request.onreadystatechange = function() {
        self.write.innerHTML = (self.request.readyState == 4) ? self.request.responseText : "Please wait…";
    }
}

XmlHttpRequest.prototype.post = function(inFile, inPost) {
    this.request.open("POST", inFile, true);
    this.request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    this.request.send(inPost);
}

When either Delete or Edit are clicked they call functions, one of which is this:

function getForm(inEntryId, inTemplateId) {
    var key = "form"+inEntryId;
    if (!requested[key]) {
        var goGet = new XmlHttpRequest(document.getElementById("hideOptions"+inEntryId));
        goGet.post("getForm.php", "id="+inEntryId+"&template="+inTemplateId);
    }
    requested[key] = true;
}

This appears to work just fine. However, after writing this I became worried that when executions ends in getForm() that it may be possible for goGet to be removed before the request finished and the process would not complete within function XmlHttpRequest(), this.request.onreadystatechange = function().

Is this something I need to be worried about (and so I have to maintain some kind of global connection to each goGet)?

Edit: I realize at this point after using it for a while that the answer is probably no, it somehow is not removed before it finished the request, what I want to know is why. Also, I decided to remove my side question (about the prototype)

Upvotes: 1

Views: 340

Answers (3)

visibleman
visibleman

Reputation: 3315

The W3C specification for XMLHttpRequests specifically states that the XMLHttpRequest can not be garbage collected under certain circumstances as explained in [XMLHttpRequest Level 1] (http://www.w3.org/TR/XMLHttpRequest/).

It is thus the responsibility of the browser to ensure that garbage collection does not take place on e.g. open XMLHttpRequests. Without looking deeper into the details, I have noticed that Chromes Heap Snapshot tool shows that the request is referenced by a number of global objects even after a local reference has gone out of scope. There have also been a number of bugs over the years relating to memory leaks with XMLHttpRequests, which I think was likely due to browser implementations not getting this right.

4.3 Garbage collection

An XMLHttpRequest object must not be garbage collected if its state is OPENED and the send() flag is set, its state is HEADERS_RECEIVED, or its state is LOADING, and one of the following is true:

It has one or more event listeners registered whose type is readystatechange, progress, abort, error, load, timeout, or loadend.

The upload complete flag is unset and the associated XMLHttpRequestUpload object has one or more event listeners registered whose type is progress, abort, error, load, timeout, or loadend.

If an XMLHttpRequest object is garbage collected while its connection is still open, the user agent must terminate the request.

Upvotes: 1

Pebbl
Pebbl

Reputation: 36035

goGet does not survive, but your XmlHttpRequest instance does

The simple answer is that goGet is garbage collected at the end of the getForm call, you won't be able to access anything using goGet after that point. However, this was just a variable that temporarily held a reference to an object in memory, an instance of XmlHttpRequest, which was used to trigger post. The instance structure itself will still remain in memory because of the next step.

internal browser processes are code too, and will retain references

At the point you call post — still within getForm and while goGet exists — you set the stored XHR going, which triggers the internal browser process for handling these requests, but only after goGet has finished execution. Because XHRs always trigger after the execution of the code that called them into existence the browser has to keep them specially "alive" in order for them to actually work.

Because the XHR object is kept alive, your already defined readystatechange handler is also kept kicking, which is a closure, that holds a reference — via self — to your original instance of XmlHttpRequest.

Whilst the XHR request is still processing the readystatechange handler will still exist, and so the entire structure you have created using XmlHttpRequest will be kept alive, until this closure is destroyed.

An informative post about this process can be found here:

http://nullprogram.com/blog/2013/02/08/

It should be also noted that garbage collection doesn't tend to be instantaneous, it usually happens in scheduled waves, so even if all references to a structure are removed, it can still sit around in memory for a unknown period of time. However, in this instance, this is not what is keeping your structure alive.

Upvotes: 2

jcaron
jcaron

Reputation: 17710

You are quite right to ask the question: according to the usual Javascript memory management, goGet and all that goes with it should be garbage-collected.

However, since it's a pretty common use pattern of XMLHttpRequest to just invoke it and forget about it, there are special rules preventing its garbage collection in these cases.

The specs says so here: http://xhr.spec.whatwg.org/#garbage-collection

4.2 Garbage collection

An XMLHttpRequest object must not be garbage collected if its state is OPENED and the send() flag is set, its state is HEADERS_RECEIVED, or its state is LOADING and it has one or more event listeners registered whose type is one of readystatechange, progress, abort, error, load, timeout, and loadend.

If an XMLHttpRequest object is garbage collected while its connection is still open, the user agent must terminate the request.

Also here: http://www.w3.org/TR/XMLHttpRequest/#garbage-collection (slightly different wording).

In practice, it seems some implementations may actually keep the XMLHttpRequests a lot longer that that. You'll find a long discussion on the topic here: http://nullprogram.com/blog/2013/02/08/

Upvotes: 6

Related Questions