4

Background

You can see from the following code:

var foo1 = new Promise (function (resolve, reject){};

var foo2 = new Promise (function (resolve, reject) {

    resolve('succes!');
});

var foo3 = new Promise (function (resolve, reject) {

    reject(Error('Failure!'));
});

console.log (typeof foo1 === 'object'); // true
console.log (Object.getOwnPropertyNames(foo1)); // []
console.log (foo1.length); // undefined

console.log (foo1); // Promise { <pending> }
console.log (foo2); // Promise { 'succes!' }
console.log (foo3); // Promise { <rejected> [Error: Failure!] }

that the variable referencing a Promise is referencing a special Promise object containing either the state or the outcome of the function you pass to the Promise constructor. If you then set:

foo1 = null;
foo2 = null;
foo3 = null;

you are no longer able to access this state or outcome.

Question

Does a Promise get garbage-collected in the above situation and if no, does that not create a risk of causing memory leaks?

rabbitco
  • 2,790
  • 3
  • 16
  • 38
  • with the exception of poorly-written code that generates too much stuff, there's almost so such thing as a memory leak in JS. nobody uses IE6/7 anymore... – dandavis Feb 25 '16 at 21:12
  • Thank you, @dandavis. Still interested in finding the answer though as a "dead" promise might sometimes hold sensitive information and also because of a geeky interest in knowing everything there is to know about JS :) – rabbitco Feb 25 '16 at 21:20

2 Answers2

4

Does a Promise get garbage-collected in the above situation?

Yes. A promise object is just like every other object in that regard.

Some implementations (Firefox) do have special behaviour where unhandled-rejection detection depends on garbage collection, but that doesn't really change anything about the promise object being collected.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you very much, @Bergi. I am unsure how garbage-collection (GC) works in relation to the function body passed to the `Promise` constructor. My own thoughts: the `Promise` only settles once and cannot be reused. So it would make sense if the function body was GC’ed right after the `Promise` settles - irrespective of whether the `Promise` instance has been GC’ed. Is the function body GC’ed and if so does this happen independent of GC of the `Promise` instance? – rabbitco Feb 26 '16 at 07:45
  • Yes, the promise instance does not hold a reference to the executor function that was passed to the constructor, they are quite independent for the GC. Of course that doesn't mean that there are can't be any other references to the executor function, e.g. when you reuse them or when the asynchronous action closes over it (like it does close over `resolve` and `reject`) – Bergi Feb 26 '16 at 12:23
  • So an async executor function is not GC'ed until it has settled irrespective of whether the `Promise` instance has been GC'ed in the mean time? – rabbitco Feb 26 '16 at 13:55
  • No, you can't generalise it like that. You need to consider an single scenario, and analyse that. – Bergi Feb 26 '16 at 14:10
0

I asked another question about this, because I didn't see this one. But the discussion here, and on other links that showed up in response to my question, doesn't give a definite answer. (Both yes and no are given as answers, and there's not enough evidence provided to know who's right.)

So I devised this test:

$ node --expose-gc
Welcome to Node.js v17.1.0.
Type ".help" for more information.
> const registry = new FinalizationRegistry(heldValue => { console.log(`finalizing with ${heldValue}`) });
> var stop = false; function loop(res) { if (stop) return res(); setTimeout((() => loop(res)), 3000); }
> var p = new Promise((res,rej) => loop(res)), q = p.then(()=>console.log('done')); registry.register(p, "pho", p); registry.register(q, "qux", q);
> p=q=null;
> gc()
undefined
> gc()
undefined
> gc()
undefined
> gc()
undefined
> gc()
undefined
> stop=true
true

done
> gc()
undefined

finalizing with qux
finalizing with pho

Summary: After p=q=null I have no references anymore to either of the Promises. So if they were gc'able before resolving, one would expect to see the finalization messages before the loop stopped and the second Promise logged "done". But one doesn't see this.

This isn't conclusive though, since presumably an internal Node registry is holding a reference to the setTimeouts while they're ticking down, and thus also to the res parameter, and via it to the first Promise. And maybe the first Promise is holding a reference to the second.

I also tried:

> var p = new Promise((res,rej) => { }), q = p.then(()=>console.log('done')); registry.register(p, "pho", p); registry.register(q, "qux", q); q = null;
> gc()
undefined
> gc()
undefined
> gc()
undefined
> gc()
undefined
> p = null
null
> gc()
undefined

finalizing with qux
finalizing with pho

This confirms that as long as you hold a reference to the first Promise, the .then(...) promise attached to it will be kept alive too, even if you don't have any explicit reference to the latter. But when you drop your reference to the first Promise, then it will become collectable even if not yet resolved, and so too will the second Promise.

If in this last example, I drop the reference to p first, keeping the reference to q, then p becomes collectable. (The reference to q in that case doesn't keep p alive.)

dubiousjim
  • 4,722
  • 1
  • 36
  • 34