skip to Main Content

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


  1. 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:

    1. this.icon is false – resolve?
    2. the image loads successfully – resolve
    3. Error loading the image – reject
    loadInstance(c) {
      return new Promise((resolve, reject) => {
        if (!this.icon) {
          resolve();
          return;
        }
        let img = new Image();
        img.src = 'testImg.png';
        img.onload = () => {
          this.image = img;
          resolve();
        };
        img.onerror = reject;
      });
    }
    

    Notes:

    • Garbage collection should clean up the images if they are not being referenced anywhere. You shouldn’t need to worry about cleaning those up.
    • Yes you need to return after resolving or the function will continue executing.
    Login or Signup to reply.
  2. 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":

    function runGame(){
      if (rOps1.status.on === true) {
        rOps1.loadInstances(c).then(() => {});
        rOps1.run(c, mPos);
        rOps1.draw(c);
      }
    
      setTimeout(() => {
        requestAnimationFrame(runGame);
      }, 40);
    
    }
    
    1. rOps1.loadInstances(c).then(() => {}); doesn’t wait for the instances to load. It synchronously continues execution and calls the run and draw methods before creating a a timer callback in 40 ms or so.

    2. 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 by loadInsances and loadIntance 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search