How Garbage Collection works in JavaScript Promises
One of our worst nightmares as developers is Out of Memory (OOM) exceptions. Ever added a then
handler to a promise, and then wondered if it's still lingering in memory, waiting to cause a dreadful OOM blowup? In this piece, we'll learn about promises with a specific focus on what happens to handlers after they're executed. We'll cover the key concepts, including the Promise Job Queue, microtasks, and garbage collection.
You have probably encountered situations where you need to initialize a service with some backend data, and you do that in a promise, and then attach handlers for when the data is back. When the logic that attach those handlers run, you may be unsure whether the promise is settled or not, and then worry about what happens to those handlers once the promise is resolved.
In Simple Terms - How do Promises Work?
When a promise is created, it's in a pending state. At this point, you can add handlers to the promise using the then
method. These handlers are stored in an internal list within the promise object. When the promise is fulfilled (using the promise's resolve()
callback) or rejected (using the β you guessed itβreject()
callback), the handlers are converted into Promise jobs, which are placed in the Promise Job Queue (also known as the microtask queue). This process discards any handlers that don't apply to the promise's settled state.
Memory Status of Handlers After Execution
Let's consider two scenarios: adding handlers to a settled promise and adding handlers to a pending promise.
// Create a promise that resolves immediately const settledPromise = Promise.resolve('Settled promise resolved!'); // Create a promise that resolves after 2 seconds const pendingPromise = new Promise((resolve) => { setTimeout(() => { resolve('Pending promise resolved!'); }, 2000); }); // Add handler to the settled promise settledPromise.then((value) => { console.log('Handler for settled promise:', value); }); // Add handler to the pending promise pendingPromise.then((value) => { console.log('Handler for pending promise:', value); });
In the example above, we have two promises: settledPromise
and pendingPromise
. When we add handlers to settledPromise
, they are executed immediately because the promise is already settled. On the other hand, when we add handlers to pendingPromise
, they are stored in the promise's internal list and executed only when the promise is resolved. No matter the number of handlers you add to a settled promise, they will not pile up; instead, they are executed immediately.
In other words, once a promise is settled, adding handlers to it does not cause memory issues because these handlers are executed and removed from the promise's internal list immediately. Conversely, adding handlers to a pending promise stores them in the promise's internal list until the promise is resolved. At that point, the handlers are converted into Promise jobs and executed. After execution, the handlers become eligible for garbage collection.
Conclusion
Hopefully, you now understand how promises are settled and how the runtime manages memory with respect to handlers attached to promises. Thanks for stopping by this one, and see you in the next!