I have a method that updates an object with a new image each loop through an animation, but causes a continuing memory leak which eventually drops the rest of the animation to a standstill. This is roughly what the method is:
loadInstance(c) {
let promise = new Promise((resolve, reject) => {
//this is required because the new image is based on another image
//"this.icon" which is loaded before the animation loop.
if (this.icon !== false) {
//create a new image:
let img = new Image();
img.src = "testImg.png";
//load the image
img.onload = () => {
//initial image is set as false, check that this is not so
if (this.image !== false) {
//sets the image's onload and src to null
this.image.src = "";
this.image.onload = null;
}
//sets the new image to the object
this.image = img;
resolve();
};
}
}); //end promise
return promise;
}
There are a few objects I need to update, so I combine the promises here:
loadInstances(c) {
let cPromise = Promise.resolve();
for (let i = 0; i < this.rOptions.length; i++) {
cPromise = cPromise
.then(() => {
return this.rOptions[i].loadInstance(c);
})
.catch((err) => {
console.error("error:", err);
});
}
return cPromise;
}
then I call the single promise in the animation loop:
//rOps1 being the main object which calls the loadInstance(c) from an array of objects through loadInstances(c)
function runGame(){
if (rOps1.status.on === true) {
rOps1.loadInstances(c).then(() => {});
rOps1.run(c, mPos);
rOps1.draw(c);
}
setTimeout(() => {
requestAnimationFrame(runGame);
}, 40);
}
requestAnimationFrame(runGame);
Naturally I thought the leak was due to the new images still being referenced in memory and so I made sure to null each prior image before updating the next. This did help significantly, but I still seem to have a constant leak from something else linked to this method. I also nulled the onload method of the previous image, but still no change. Any ideas?
2
Answers
If a promise never resolves it may hang out in memory (provided there’s a reference to it).
If you have conditional paths in one, make sure to resolve or reject in every path. Usually a guard statement with a return is simpler than a big conditional block.
In this case you have 3 paths:
this.icon
is false – resolve?Notes:
Have a look at those
.then(()={}
clauses. They don’t achieve anything, apart from adding an extra job to the promise job queue. They do not provide any kind of synchronous delay for code calling the functions they reside in.There is a timing hole in
runGame
that is waiting to cause a problem and, I suspect, is the cause of your "memory leak":rOps1.loadInstances(c).then(() => {});
doesn’t wait for the instances to load. It synchronously continues execution and calls therun
anddraw
methods before creating a a timer callback in 40 ms or so.If the promise returned by
loadInstances
takes longer than 40ms to become fulfilled on average, memory usage will continue to grow as the number of pending promises created byloadInsances
andloadIntance
increases.There may be other issues affecting memory usage, but this one should be fixed first – trying to predict the cause of bad behavior in a program with known bugs is generally a waste of time.