Yeo Bryan
Yeo Bryan

Reputation: 429

Wait for function to finish running before iterating

How can I wait for the image.onload function to finish running before iterating the next value in an array??

for (let i = 0; i < Object.keys(images_list_complex_form).length; i++) {
    for (let k = 0; k < images_list_complex_form[i].length; k++) {
        console.log('ITERATE');
        image.onload = function () {
            console.log('STARTED IMAGE ON LOAD');
        }
    }
}

I am receiving multiple 'iterates' before receiving started image on load

As sugguested by @amadan, i have tried adding in async and await into the for loop however, after doing so the image111.onload is never triggered anymore. I never get to see the console log "Started image on load"

        async function rectifyMissingSavedImages(images_list_complex_form_array,annotationIndexArray){
            console.log(images_list_complex_form_array);
            for(let i = 0; i < Object.keys(images_list_complex_form_array).length; i++){
                for(const[index2,value2] of Object.entries(images_list_complex_form_array[i])){
                    if (Object.keys(annotationIndexArray).length == 0 || (!annotationIndexArray[i] || annotationIndexArray[i].includes(String(k)) == false)) {
                        let image_url = URL.createObjectURL(value2);
                        let image111 = new Image();
                        let canvas = new fabric.Canvas();
                        
                        console.log(image111);
                        await new Promise((resolve,reject) =>{
                            image111.onload = evt => {
                                console.log("??");
                                console.log('STARTED IMAGE ON LOAD');
                                let background = new fabric.Image(image);
                                canvas.setHeight(background.height);
                                canvas.setWidth(background.width);
                                canvas.setBackgroundImage(background, canvas.renderAll.bind(canvas));
                                console.log("ABOUT TO RESOLVE");

                                image.src = image_url;
                                resolve();
                            }
                        });

Upvotes: 0

Views: 69

Answers (2)

t.niese
t.niese

Reputation: 40842

That task is not completely trivial. At least if src is already set (the load of the image will start as soon as you set src).

So if the src is already set you need to determine two cases:

  1. image already loaded
  2. is still loading

This can be check with with HTMLImageElement.complete

Furthermore, you need to check for both the load or error case otherwise your loop might get stuck in case of a network error or when something else goes wrong with loading the image.

// create a function to which you can pass the image
// that function will determine if the image was already loaded
// or it has to wait for the image to be loaded
function waitForImage(image) {
  return new Promise((resolve, reject) => {

    // first check if image was already loaded
    if (image.src && image.complete) {
      // check if loading was successful
      // there might be better ways then checking its dimension
      // because this will fail if you load an image that has no size (like tracking pixels)
      if (image.width > 0 || image.height > 0) {
        resolve()
      } else {
        reject()
      }
    } else {
      // resolve in case a successful load
      image.onload = resolve
      // reject in case of an error
      image.onerror = reject
    }
  })
}

async function loadImages() {
  // create some dummy data to make the snipped runable
  let urls = [
    'https://via.placeholder.com/350x150',
    'https://via.placeholder.com/350x150',
    'https://via.placeholder.com/350x150',
    'https://via.placeholder.com/350x150',
    'https://via.placeeeeeeeholder.com/350x150', // intentional typo to make it fail
    'https://via.placeholder.com/350x150'
  ]

  let images = urls.map(url => {
    let image = new Image()
    image.src = url
    return image
  })

  // iterat over the images
  for (let image of images) {
    try {
      // call a function that returns a Promise 
      // and use await wait for the image to load or fail loading
      await waitForImage(image)
      console.log(`loading ${image.src} finished`)
    } catch (err) {
      console.log(`loading ${image.src} failed`)
    }
  }
}

loadImages();

Upvotes: 2

Amadan
Amadan

Reputation: 198304

The easiest way is to create and await a promise inside the loop. Something like:

async function loadImages() {
  for (...) {
    await new Promise((resolve, reject) => {
      image.onload = evt => {
        console.log('STARTED IMAGE ON LOAD');
        resolve();
      }
      image.onerror = reject;
    });
  }
}

This is the only way that a loop can "pause" in JavaScript. Any other way involves either chaining promises, which is messy, or nesting callbacks, which is even worse.

Upvotes: 6

Related Questions