HackNode
HackNode

Reputation: 189

Javascript - Wait for array to finish populating

I am trying to get the value of some array elements. It works for the elements [0], [1], [2], [3], but not [4].

function getBase64() {
  const urls = ['https://i.imgur.com/egNg7JU.jpg',
    'https://i.imgur.com/RLZ7WH1.jpg', 'https://i.imgur.com/qfabBbA.jpg',
    'https://i.imgur.com/Zuh1KaX.jpg', 'https://i.imgur.com/yD7X6Q1.jpg'
  ];

  let base64urls = [];

  const start = async () => {
    await asyncForEach(urls, async (num) => {
      await waitFor(50)
      toDataURL(num, function(dataURL) {
        base64urls.push(dataURL);
      });
    })
    console.log(base64urls);
    console.log(base64urls[4]);
  }
  start()

}

async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}

const waitFor = (ms) => new Promise(r => setTimeout(r, ms))

toDataURL simply returns the base64 value of an image. Whenever I try console.log(base64urls[4]), it returns 'undefined'. I do get the correct value for the previous elements. Is there some way to restructure this, or perhaps use a different method of waiting for the array to completely populate before checking for the values of its elements?

EDIT

Here is my toDataURL

function toDataURL(src, callback) {
  const image = new Image();
  image.crossOrigin = 'Anonymous';

  image.onload = function () {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.height = this.naturalHeight;
    canvas.width = this.naturalWidth;
    context.drawImage(this, 0, 0);
    const dataURL = canvas.toDataURL('image/jpeg');
    callback(dataURL);
  };

  image.src = src;
}

Upvotes: 4

Views: 9382

Answers (2)

Melchia
Melchia

Reputation: 24324

You can use promise.all for this kind of situation to wait for the results of your queries

const urls = ['https://i.imgur.com/egNg7JU.jpg', 
'https://i.imgur.com/RLZ7WH1.jpg', 'https://i.imgur.com/qfabBbA.jpg', 
'https://i.imgur.com/Zuh1KaX.jpg', 'https://i.imgur.com/yD7X6Q1.jpg'];

let base64urls = [];
 
Promise.all(urls.map(url => fetch(url))).then(res => toBase64DataURL(res)).then(result => {base64urls.push(result.toDataURL());
console.log(base64urls);});

function toBase64DataURL(src) {
  return new Promise(resolve => {
const image = new Image();
image.crossOrigin = 'Anonymous';

image.onload =  _=> {
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  canvas.height = this.naturalHeight;
  canvas.width = this.naturalWidth;
  context.drawImage(this, 0, 0);
  const dataURL = canvas.toDataURL('image/jpeg');
  resolve(dataURL);
};
image.src = src;
  });
}
  

Upvotes: 2

CertainPerformance
CertainPerformance

Reputation: 371198

It looks like toDataURL is asynchronous and callback-based - either change it so that it returns a Promise and await that Promise, or pass a Promise's resolve into the callback:

async function getBase64() {
  const urls = ['https://i.imgur.com/egNg7JU.jpg', 
                'https://i.imgur.com/RLZ7WH1.jpg', 'https://i.imgur.com/qfabBbA.jpg', 
                'https://i.imgur.com/Zuh1KaX.jpg', 'https://i.imgur.com/yD7X6Q1.jpg'];
  const base64urls = [];
  for (const url of urls) {
    const dataURL = await new Promise(resolve => toDataURL(url, resolve));
    base64urls.push(dataURL);
  }
  console.log(base64urls);
  console.log(base64urls[4]);
}

If you want to change your toDataURL function to return a Promise so you don't have to treat it like a callback:

function toDataURL(src) {
  return new Promise(resolve => {
    const image = new Image();
    image.crossOrigin = 'Anonymous';

    image.onload = function () {
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      canvas.height = this.naturalHeight;
      canvas.width = this.naturalWidth;
      context.drawImage(this, 0, 0);
      const dataURL = canvas.toDataURL('image/jpeg');
      resolve(dataURL);
    };
    image.src = src;
  });
}

and then const dataURL = await toDataURL(url)

Upvotes: 7

Related Questions