3

Assume you have the following code:

function startApp() {
    createReallyLongRunningPromise();

    // intensive work with memory
}

Is there any way that the created promise will get GC'd before it's done or is it guaranteed to finish working as long as the process is live?

If it's relevant, the long-running promise's body returns a promise of itself:

function createReallyLongRunningPromise() {
    return new Promise(resolve => {
        // Stuff

        return promiseCreatedDuringStuff;
    });
}
Omer van Kloeten
  • 11,800
  • 9
  • 42
  • 53
  • 1
    Your code looks awfully like the [`Promise` constructor antipattern](http://stackoverflow.com/q/23803743/1048572)… but that doesn't matter for garbage collection. – Bergi Dec 06 '16 at 22:30
  • If you just throw away the reference to `resolve`, the promise will never be settled but is eligible for garbage collection. – Bergi Dec 06 '16 at 22:34
  • @Bergi Thanks you for the pointer. I've looked into it, but it's not the case in the actual code, only the repro looks similar. – Omer van Kloeten Dec 07 '16 at 08:02
  • What are you actually worried about here? You don't show enough real code to see your actual situation and figure out what you actually are worried about or curious about here. – jfriend00 Dec 07 '16 at 17:47
  • @jfriend00 If promise p has a then(f) and p is collected before it completes, upon completion, then(f) may not be called since f was collected by GC (being that p had the only reference to f). – Omer van Kloeten Dec 08 '16 at 07:44
  • Well, no object that is still reachable by code will be GCed so you have nothing to worry about. By definition, GC won't take an object that can still be used by any code. – jfriend00 Dec 08 '16 at 07:50
  • My issue here is that f is no longer reachable since p has been collected while it is running (since nothing references it). – Omer van Kloeten Dec 08 '16 at 08:04
  • 1
    But, when the promise is resolved or rejected, we know that `.then()` can be triggered so whatever is keeping track of `.then()` handlers has to have a live reference to it so that cannot be collected until `resolve()` or `reject()` can never be called or have already been called. The reference is internal, not in your code. It's how `.then()` handlers are connected to `resolve()` and `reject()` for that promise. Maybe a little hard to explain, but it is not a problem. The underlying async operation keeps the promise alive, even if you don't. – jfriend00 Dec 08 '16 at 08:18

2 Answers2

2

Is there any way that the created promise will get GC'd before it's done or is it guaranteed to finish working as long as the process is live?

Those are two completely separate, and someone-unrelated, questions. :-)

If nothing has a reference to the promise object, then naturally it can be GC'd. The promise implementation details would dictate whether the resolve function has access to the promise object (as opposed to something deeper that the promise object also references); if it does, then any outstanding references to resolve would also keep the promise around. If it doesn't, then if nothing else does, the object can be GC'd.

But that has nothing to do with the asynchronous process that's reporting completion via promise, other than that that process probably has an active reference to resolve and thus keeps that resolve function alive along with anything that resolve function references.

So unless the environment is terminated, the asynchronous process should complete. You haven't said where you're doing this, but for instance NodeJS keeps the environment alive while there are any outstanding timers, I/O requests, etc.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
2

Is there any way that the created promise will get GC'd before it's done

No. Assuming the async operation still has not completed and still has the ability to resolve or reject the promise, then it retains some sort of reference to the promise object so it cannot be GCed.

A promise object is just a normal Javascript object. It will be garbage collected exactly the same as any other Javascript object which is when no active, reachable code has a reference to the promise object any more. A reference can be held to the promise either by the asynchronous operation while it's underway or by the initiator of the async operation and user of the promise (most likely by both).

As long as an asynchronous operation is underway and some part of that asynchronous operation has a reference to either the resolve or reject callbacks from the promise executor function, then there's a reference to the promise object that is still alive and it is simply not eligible for garbage collection.

Once the asynchronous operation completes, calls its completion callback and does whatever it intends to do with resolve() or reject() and all .then() handlers have been called, then the async operation itself probably no longer has a reference to the promise object. So, when the promise object would be eligible for garbage collection would depend entirely upon the calling code and how long it retains a reference to the promise. If that promise was stored in a variable, then the reference will stay alive until either the variable itself is GCed or until the variable is assigned some other value.

or is it guaranteed to finish working as long as the process is live?

There is no guarantee that a promise is ever resolved or ever eligible for garbage collection. A promise that is stored in a long lasting variable (e.g. a global or some persistent state) will simply never be eligible for garbage collection.

Similarly a promise that starts an async operation that itself retains a reference to the resolve or reject functions from the promise executor, but never actually completes and stays in scope itself will just create a dangling promise that never resolves or rejects.

As an example if you used a webSocket connection to send a message to a remote host and you set up a promise to resolve when a specific response message comes back from the remote host, but the response message never arrives back, then that promise would just be waiting forever for the response message to come back. Because it's still "active" and the response message could still arrive some time in the future, nothing is eligible for garbage collection.

That promise dangles forever and is not garbage collected. Now, in the real world, one should probably implement some sort of timeout that would reject the promise if the response didn't come back in some sort of time frame and that would prevent the dangling promise lasting forever because it was still waiting for a response.

And, if the caller holds onto a reference to the promise forever, then the promise will never be eligible for garbage collection. Consider this trivial example in the global scope (or any lasting scope like a loaded module scope):

var p = new Promise(function(resolve) {
    setTimeout(resolve, 1000);
});

p.then(function() {
    console.log("timer fired, promise resolved");
});

If this variable p never goes out of scope and thus lasts forever, then the promise help in it will never be eligible for garbage collection. This is no different than any other object assigned to p.


Now, consider this example:

var p = new Promise(function(resolve) {
    setTimeout(resolve, 1000);
});

p.then(function() {
    console.log("timer fired, promise resolved");
});
// clear variable p
p = null;

Here, the variable p has been cleared so it no longer retains a reference to the promise object. As such, the promise will be eligible for garbage collection as soon as the p.then() handler has run because no active code will any longer have a reference to that promise`.


Now, most of the time you don't have to manually clear a promise variable like this. For example, if the code above was just written like this:

new Promise(function(resolve) {
    setTimeout(resolve, 1000);
}).then(function() {
    console.log("timer fired, promise resolved");
});

Then, no lasting variable p is created in the first place.


Or, if this was inside a function scope that itself would finish such as:

function runDelay() {
    var p = new Promise(function(resolve) {
        setTimeout(resolve, 1000);
    });

    p.then(function() {
        console.log("timer fired, promise resolved");
    });
}

runDelay();

Then, the variable p itself goes out of scope after the promise fires so both it and the promise itself are eligible for GC after the promise resolves.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • thank you, that's exactly the explanation I was missing! – Omer van Kloeten Dec 07 '16 at 08:04
  • *"No. Assuming the async operation still has not completed and still has the ability to resolve or reject the promise, then it retains some sort of reference to the promise object so it cannot be GCed."* Have you examined the Bluebird source code to check that that's actually the case? Because in one obvious implemention it wouldn't be. As I said in my answer, if the `resolve` function *doesn't* have a reference to the Promise but instead to a deeper object that both it and the Promise reference, there's no reason the Promise couldn't be GC'd if unreferenced otherwise. – T.J. Crowder Dec 07 '16 at 08:12
  • @T.J.Crowder - That seems like a purely semantic argument. The point is that there is an object involved in the implementation of promises that will not be GCed when resolve or reject can still be called by reachable code. And, certainly the external promise object itself will not be GCed when anyone could still be using it. If nobody could still be using it, then nobody is bothered when it is GCed. – jfriend00 Dec 07 '16 at 16:58
  • @jfriend00: In the code in the question, no one is using it. I don't think it's just semantics at all. There's a difference between the promise object and the implementation mechanism of promises. In the above, you state clearly that the promise object will not be GC'd. That's far too strong, not least because the `resolve` function almost certainly *doesn't* have a reference to the external promise, as that would leave access to internals of the promises accessible, which would be poor design. – T.J. Crowder Dec 07 '16 at 17:02
  • @T.J.Crowder - Now you're jumping to conclusions. `resolve` could very well be a regular function that has a closure where it has private access to the promise internals and the external promise object which is in fact exactly what Bluebird does [as seen here](https://github.com/petkaantonov/bluebird/blob/master/src/promise.js#L506). There are lots of different ways it could be implemented. The OP doesn't show enough real code for me to have any idea what their code is actually doing so I answered a purely hypothetical. – jfriend00 Dec 07 '16 at 17:33
  • @T.J.Crowder - I get that it's possible for an implementation to be such that the external promise object could be GCed before `resolve()` or `reject()` is called if nobody has a reference to the outer promise object, but I rather doubt that's what the OP is asking because if nobody has a reference to the outer promise object, then it's likely immaterial when it is GCed. And, if the concern was memory management, then there's still at least another internal promise object that can't be GCed until `resolve()` or `reject()` are called or can no longer be called so you reach the same conclusion. – jfriend00 Dec 07 '16 at 17:36
  • @jfriend00: That code is convoluted, but it does *seem* like that's the case with that implementation, yes. As for the other, the OP said "the created promise" not "the promise object" (which is what I read), so while it's up to them to clarify or not, but that could definitely be interpreted as inclusive of anyting related to the promise. :-) – T.J. Crowder Dec 07 '16 at 17:39
  • @T.J.Crowder - Yeah, the code is hard to follow from source so I just stepped into `resolve()` from an executor to see where it led me and landed in a closure. – jfriend00 Dec 07 '16 at 17:39