简体   繁体   中英

javascript asynchronous callback lifetime

I am new to javascript but have some c++ background, so the following confuses me: I create some WebSocket connection from browser like this:

function ready() {
    var connection = new WebSocket('ws://localhost:3000');

    connection.onmessage = function(msg) {        
        console.log(msg.data);
    };
}

document.addEventListener("DOMContentLoaded", ready);

the connection variable is local so the reference to WebSocket object goes out of scope when ready() exits. My expectation is that the connection should die along with the callback on it, but somehow I am still able to get connection.onmessage callbacks. My understanding that it's not related to javascript scoping but a matter of how WebSocket connections are organized internally. Is it so? Could you explain this behavior please. I believe the same goes for every asynchronous api in javascript, another example:

function get(url) {
    var req = new XMLHttpRequest();
    req.open("GET", url);

    req.onload = function() {
        console.log(req.response);
    }

    req.send();
}

get("file.json");

So it seems like callbacks are not tied to objects they are put on. Thanks in advance!

Javascript is a garbage collected language. That means that an object or contents of a variable in Javascript is NOT automatically freed when it goes out of scope in a function. Instead, the object lives until there is no more active code that can reach it.

So, in your function, you set up an event handler on the connection object. As long as there is active code somewhere that still has a reference to that connection object, then Javascript will not garbage collected that object.

In this particular case, the connection object represents a live TCP connection to another host and there's both Javascript code and native code behind that TCP connection that still has a reference to the connection object and can still fire events on it. The Javascript engine knows that this code still has a reference to this object so it will not garbage collect it. And, sure enough anytime a new packet arrives on the webSocket connection, it will call your onMessage handler.

The way to make that connection object eligible for garbage collection would be to close the connection. That will cause the webSocket code and the native code behind it to clear any references it has to the connection object and it will then be eligible for garbage collection.

In Javascript, one should never think of function local variables as having a lifetime strictly associated with when the function returns. You will shortly find that (particularly due to the asynchronous nature of many operations in Javascript) that local variables in a function stay alive long after a function returns and this turns out to be very, very useful.

The mental model that I use (which may or may not be exactly how it's implemented) that helps me understand all this is to never think of local variables in a function as being allocated on a stack frame. Instead, think of them as dynamically allocated on a heap. They can live on that heap for as long as needed, including long after the function has already returned. The garbage collector will free them when no code is using them any more or could use them any more.

Because of asynchronous callbacks (like in your example), such a callback can easily cause the local variables to stay alive long after the containing function has already returned.

the connection variable is local so the reference to WebSocket object goes out of scope when ready() exits

Things in function scope are not automatically freed when the containing function returns. They are not allocated on the stack like they would be in C/C++. They are garbage collected and will be freed when no longer being used.

My expectation is that the connection should die along with the callback on it, but somehow I am still able to get connection.onmessage callbacks

See above. Local variables in Javascript can live long after their host function returns.

My understanding that it's not related to javascript scoping but a matter of how WebSocket connections are organized internally.

This is not particularly webSocket-specific. As described above, this is because of Javascript garbage collection. The code that manages the (still alive) webSocket connection has a live reference to the connection object and thus prevents it from being garbage collected. To terminate that live reference, you would close the webSocket connection and then the code that manages the webSocket connection would clear it's references to the connection object and then it would be eligible for garbage collection.

Event listeners in JavaScript are more like subscriptions. Until you unsubscribe you will continue to receive information. If you want to close this off after receiving one message, you can set your onload function to close the connection immediately after getting information:

function ready() {
    var connection = new WebSocket('ws://localhost:3000');

    connection.onmessage = function(msg) {        
        console.log(msg.data);
        // this closes the websocket connection
        connection.close();
    };
}

document.addEventListener("DOMContentLoaded", ready);

Update

In general your concern is about how closures work in JavaScript. I can't link to it directly but if you go here and scroll up 3 code blocks there's an example regarding callbacks and closures. It's more in regards to iterations but has some relevant pieces of information. The entire article is definitely worth reading to get a better handle on some of the nuances of scopes in JavaScript.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM