Does Never Resolved Promise Cause Memory Leak

Does never resolved promise cause memory leak?

Well, I'm assuming you don't keep an explicit reference to it since that would force it to stay allocated.

The simplest test I could think of is actually allocating a lot of promises and not resolving them:

var $q = angular.injector(["ng"]).get("$q");
setInterval(function () {
for (var i = 0; i < 100; i++) {
var $d = $q.defer();
$d.promise;
}
}, 10);

And then watching the heap itself. As we can see in the Chrome profiling tools, this accumulates the needed memory to allocate a 100 promises and then just "stays there" at less than 15 megabyes for the whole JSFIddle page

Sample Image

From the other side, if we look at the $q source code

We can see that there is no reference from a global point to any particular promise but only from a promise to its callbacks. The code is very readable and clear. Let's see what if you do however have a reference from the callback to the promise.

var $q = angular.injector(["ng"]).get("$q");
console.log($q);
setInterval(function () {
for (var i = 0; i < 10; i++) {
var $d = $q.defer();
(function ($d) { // loop closure thing
$d.promise.then(function () {
console.log($d);
});
})($d);
}
}, 10);

Sample Image

So after the initial allocation - it seems like it's able to handle that as well :)

We can also see some interesting patterns of GC if we let his last example run for a few more minutes. We can see that it takes a while - but it's able to clean the callbacks.

Sample Image

In short - at least in modern browsers - you don't have to worry about unresolved promises as long as you don't have external references to them

Awaited but never resolved/rejected promise memory usage

Preface (you probably know this!):

await is syntactic sugar for using promise callbacks. (Really, really, really good sugar.) An async function is a function where the JavaScript engine builds the promise chains and such for you.

Answer:

The relevant thing isn't so much whether the promise is settled, but whether the promise callbacks (and the things they refer to / close over) are retained in memory. While the promise is in memory and unsettled, it has a reference to its callback functions, keeping them in memory. Two things make those references go away:

  1. Settling the promise, or
  2. Releasing all references to the promise, which makes it eligible for GC (probably, more below)

In the normal case, the consumer of a promise hooks up handlers to the promise and then either doesn't keep a reference to it at all, or only keeps a reference to it in a context that the handler functions close over and not elsewhere. (Rather than, for instance, keeping the promise reference in a long-lived object property.)

Assuming the debounce implementation releases its reference to the promise that it's never going to settle, and the consumer of the promise hasn't stored a reference somewhere outside this mutual-reference cycle, then the promise and the handlers registered to it (and anything that they hold the only reference for) can all be garbage collected once the reference to the promise is released.

That requires a fair bit of care on the part of the implementation. For instance (thanks Keith for flagging this up), if the promise uses a callback for some other API (for instance, addEventListener) and the callback closes over a reference to the promise, since the other API has a reference to the callback, that could prevent all references to the promise from being released, and thus keep anything the promise refers to (such as its callbacks) in memory.

So it'll depend on the implementation being careful, and a bit on the consumer. It would be possible to write code that would keep references to the promises, and thus cause a memory leak, but in the normal case I wouldn't expect the consumer to do that.

Memory leak in promise

My question is are there any memory leaks in the initial promise created which is ever pending since I clear the timeId so it's never gonna be settled.

No, a promise that is never resolved or rejected is just an object in Javascript like any other object. It will be garbage collected when there is no longer any code that has a reference to the object (when it becomes "unreachable").

Remember, promises are just a notification system. All they are is a regular Javascript object that serves as clearinghouse for success and failure listeners and a latched success value or failure reason for some event that completes some time in the future. They are just an object with methods like any other object. When, they become unreachable, they will be garbage collected at that time whether they are resolved, rejected or still pending.

Does JavaScript promise create memory leaks when not rejected or resolved?

The only reason this causes a memory leak is because p is a global variable. Set p = null; at the end, or avoid using a global variable:

var console = { log: function(msg) { div.innerHTML += msg + "<br>"; }};

Promise.race(new Array(10).fill(0).map(function(entry, index) {
return (function(index) {
return new Promise(function(resolve) {
setTimeout(function() {
var success = Math.random() > 0.7;
console.log((success? "R":"Not r") + "esolving "+ index +".");
success && resolve(index);
}, Math.random() * 5000 + 200);
});
})(index);
})).then(function (res) {
console.log("FOUND: " + res);
}).catch(function (err) {
console.log("ERROR: " + err);
});
<div id="div"></div>

Does using Promise and not rejecting it cause memory leak?

Throwing an error will automatically reject the Promise. Read more about it here

But there is something to discuss about. Look at the following code. The code throw an error.the error is thrown from the inside of a promise. It will automatically rejected and initiate the catch chain.

function test(value){
return new Promise(function (fulfill, reject){

throw e;

});
}

test('sample text').then(result=>console.log(result)).catch(result=>console.log(result))

But what if I've used a Web API e.g setTimeout() inside my promise. Look at the following code:

function test(value){
return new Promise(function (fulfill, reject){
setTimeout(function(){
throw new Error('haha');
},1000)

});
}

test('sample text').then(result=>console.log(result)).catch(result=>console.log(result))

Web APIs are asynchronous. Whenever a Web API is called from inside a promise, the JavaScript engine take that async code outside for execution. In simpler words, web APIs or asynchronous code gets executed outside of the main call stack.

So, throwing an error from setTimeout() won't have any reference of the caller promise, thus can't initiate the catch block. You need to reject() it from setTimeout() to initiate the catch block if there is any error.

Will it cause a memory leak?

Answer: no

the test().then().catch() will be garbage collected as soon as it finished executing. But if you would've kept the promise in a global variable like var p = test(); p.then().catch() then the variable p will stay in memory, it won't be garbage collected. But that's not a memory leak. Memory leak is a completely different aspect and doesn't apply in this kind of scenario.

Does promise resolved in n-th setTimeout cause memory leak?

The first time you call _call() in here:

async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}

It will not call the resolve callback and it will return a rejected promise and thus the new Promise() you have in getBalance() will just do nothing initially. Remember, since _call is marked async, when you throw, that is caught and turned into a rejected promise.

When the timer fires, it will call resolve() and that will resolve the getBalance() promise, but it will not have a value and thus you don't get your balance. By the time you do eventually call resolve(response.balance), you've already called that resolve() function so the promise it belongs to is latched and won't change its value.


As others have said, there are all sorts of things wrong with this code (lots of anti-patterns). Here's a simplified version that works when I run it in node.js or in the snippet here in the answer:

function delay(t, val) {    return new Promise(resolve => {        setTimeout(resolve.bind(null, val), t);    });}

var counter = 1;
function post() { console.log(`counter = ${counter}`); // modified counter value to 100 for demo purposes here return (counter++ < 100) ? { busy: true } : { balance: 3000 };}
function getBalance () {
async function _call() { let response = post();
if (response.busy) { // delay, then chain next call await delay(10); return _call(); } else { return response.balance; } }
// start the whole process return _call();}
getBalance() .then(balance => console.log('balance: ', balance)) .catch(e => console.error('some err: ', e))


Related Topics



Leave a reply



Submit