I often fall on a pattern of this kind:
function makeSureDataWasFetched () {
if (dataAlreadyFetched) {
// Creating a new empty resolved promise object and returning it
// to keep the function interface consistent
return Promise.resolve()
} else {
return fetchData()
}
}
So to avoid re-creating a new empty resolved promise object every time, I have been playing with the idea of sharing a unique, global resolved promise
const resolved = Promise.resolve()
// then, re-use this resolved promise object all over the place
function makeSureDataWasFetched () {
if (dataAlreadyFetched) {
// Return the shared resolved promise object,
// sparing the creation of a new resolved promise object
return resolved
} else {
return fetchData()
}
}
function makeSureSomethingElseWasFetched () {
if (thatSomethingElseWasAlreadyFetched) {
return resolved
} else {
return fetchSomethingElse()
}
}
// etc
Being referenced all over the application, this resolved promise will never be garbage collected. Thus, if it keeps some reference to promise chains using it, those wouldn't be garbage collected either and that would create a memory leak, right?
So my question: would this kind of global resolved promise keep a reference to all those depending promise chains in Bluebird implementation? In vanilla ES6 Promises? If not, would that have any performance downside counter-balancing the spared cost of creating new resolved promises every time?
Would sharing a global resolved promise between many promise chains create a memory leak or have performance downsides?
No.
Reusing and sharing the globally resolved promise just keeps that single globally resolved promise from ever being garbage collected. It does not affect the garbage collection of other promises that may be chained to it. They will be garbage collected when they are no longer reachable just as normally happens.
Now, it is unclear what advantage there is to sharing a globally resolved promise at all. It should not be needed. Anytime you want an already resolved promise, you can just create one with Promise.resolve()
and then your code does not rely on a shared global and can be more modular.
So my question: would this kind of global resolved promise keep a reference to all those depending promise chains in Bluebird implementation?
No.
In vanilla ES6 Promises?
No.
If not, would that have any performance downside counter-balancing the spared cost of creating new resolved promises every time?
You are asking about a premature micro-optimization which is pretty much never a good idea. If at some point in the future, you wanted to totally optimize the performance of your code, then you would profile and measure and I can promise you that you'd find 100 things you could work on that would impact your code far more than trying to share a global for a resolved promise.
To help you understand who has a reference to what in promise chaining and when things can be garbage collected, let me describe how chaining works. Let's say you have the following code:
f1().then(f2).then(f3)
f1
and f2
both return promises which we will call P1
and P2
.
So, here's the progression:
f1()
and it returns a promise P1
.P1.then(f2)
which returns a new promise we will call P3
.P3.then(f3)
which returns a new promise we will call 'P4'..then()
handlers.f2
gets called and it returns promise P2
..then()
code gets a return value from the f2
.then()
handler as a return value, it detects that this return value is a promise and it then chains that new promise P2
to P3
. It does that by adding it's own .then()
handler to P2
so it can track its state. Internal to P3
, it adds this new .then()
handler to a list of things that have to happen before P3
can be resolved. Note that there's no direct reference between P2
and P3
. They don't even have direct references to each other. Instead, P3
is waiting for a specific .then()
handler (that happens to be attached to P2
) to be called.P2
resolves..then()
handler that was established in step 6 and any reference to that .then()
handler is cleared. This tells P3
that it can now resolve itself and this unlinks its indirect reference to P2
. At this point nothing in this promise chain has any reference to P2
any more so if there are no other references to it elsewhere in your code, it can be GCed.P3
resolves, it calls its .then()
handlers which will execute f3
telling your code that the promise chain is now done.So, from this I'm hoping you can see that chained promises don't actually store references to each other. The parent promise ends up with a .then()
handler on the child promise which keeps the child promise from getting GCed until the .then()
handler is called and once that .then()
handler is called, even the indirect connection between the two promises is severed and each is independently available for GC (as long as there are no other references to them in other code).
Per your question, if P2
happens to be your shared, global promise that is already resolved, then step 6 would just add a .then()
handler to it which would be called on the next tick (since the underlying promise is already resolved) and once the .then()
handler is called, there would be no connection at all to P2
any more, thus it doesn't matter that it's a persistent global or not.