1

edited to remove a part of the question that was a duplicate

I have a page that loads a bunch of images into a gallery and then rearranges them nicely via vanilla JS.

In order to do so, the script must wait until all images have been loaded (or timed out). Changing settings to the page (like a user setting a filter) causes an ajax script to reload or add images. They are sent as a prerendered HTML page. In this case, when the script runs immediately, the images have not been fully loaded, missing crucial information for the script like width and height. So I need to wait until I have this info (=all images are fully loaded).

This is the idea behind my approach: I try to fetch all images in this part of the DOM by their class name, add a promise to each of it, that resolves if the image is fully present or a short timeout is over. I want to add all these promises to an array that I want to check then with Promise.All(). This resolved should call the script to rearrange the pictures.

I never worked directly with promises before and I also kind of noob with JS.

Here is what I have:

function articleImageLoadingStatusChecker() {
    const articleImages = Array.from(document.getElementsByClassName("articleimage"));
    var listOfPromises = [];

    articleImages.forEach(image => function(image) {
        listOfPromises.push(
            new  Promise(resolve => {
                image.addEventListener('load', () => { resolve(true); });
            }));
    });
    return listOfPromises;
}

Promise.all(articleImageLoadingStatusChecker()).then(adjustImagesForGalleryView());

Here are my problems:

  1. I am not sure if I implemented the promise correctly, so it could actually be checked by Promise.all().

  2. I asume that addEventListener('load',..) could be misleading and not actually be the correct state to ask for. Images could be cached or already loaded when the gallery script is called and so the listener may not be called. I´d rather have something that resolves the promise in any situation if the image is fully loaded. Also, I currently have no clue how to add something that resolves the promise after 1 second in case the image cannot be loaded.

Currently, this does not work at all and I run out of ideas. Any suggestions are very welcome. Thank you.

Levi Pike
  • 133
  • 6

1 Answers1

2

document.getElementsByClassName() returns an HTMLCollection array-like object which does not have the forEach method.

To solve this you could transform the HTMLCollection to an Array with the Array.from() function or with the spread syntax ....

Array.map() is a nice addition here to loop over the articleImages array and return a Promise for each image element. I'd suggest that you resolve the image element itself. This way you can use the elements later on in the promise chain.

To resolve the promise after 1 second, add both a timeout before the event listener. In the event listener, clear the timeout to prevent the timeout callback to run whenever the load event is first.

In the promise chain, only reference adjustImagesForGalleryView and do not call it (remove the ()).

function articleImageLoadingStatusChecker() {
  const articleImages = Array.from(document.getElementsByClassName("articleimage"));

  return articleImages.map(image => 
    new Promise(resolve => {
      let timeout = setTimeout(() => {
        resolve(image);
      }, 1000);

      image.addEventListener('load', () => {
        clearTimeout(timeout);
        resolve(image);
      });
    })
  );
}

Promise
  .all(articleImageLoadingStatusChecker())
  .then(adjustImagesForGalleryView);
Emiel Zuurbier
  • 19,095
  • 3
  • 17
  • 32