0

This is the code snippet. Here, I want to check if an image's width OR heigh exceeds 100, I want to return true so that I can restrict uploading such file/image to the array.

const getHeightWidth = async () => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    await reader.addEventListener('load', async (e) => {
        const image = new Image();
        image.src = reader.result;
        await image.addEventListener('load', function () {
            const { height, width } = this;
            invalidFileHeightWidth = !!((height !== 100 || width !== 100));
            return invalidFileHeightWidth;
        });
    });
};

I tried Promise.resolve as well as a direct return statement but no luck so far

Diego D
  • 6,156
  • 2
  • 17
  • 30
  • 5
    what do you expect `await image.addEventListener` to do? `addEventListener` does not return a promise, thus it cannot be awaited ... – derpirscher Feb 01 '23 at 12:57
  • Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – derpirscher Feb 01 '23 at 13:01
  • @allskeptics I think the question is: how can I have an async function (like I attempted to do) that will perform an action based on async services (FileReader.readAsDataURL) involving callback chains and return their result as a promise? – Diego D Feb 01 '23 at 13:04
  • BTW your check does not check whether one of the dimensions exceeds 100 (exceeding meaning: "being greater than") but will will return invialid if your image is not exactly 100x100 pixels. Based on your question, the correct condition would be `invalidFileHeightWidth = height > 100 || width > 100` (not sure why you put the `!!` in front of it because the result of your `||` will always be a boolean) – derpirscher Feb 01 '23 at 13:04
  • @derpirscher - Yes, the image is exact 100x100 pixels. I have removed await from the image.addEventListener and it still returns undefined – sha_script_ui Feb 01 '23 at 13:08
  • @sha_script_ui Of course it's still undefined as your `getHeightWidth` function does not return anything. Have you read the link I posted? – derpirscher Feb 01 '23 at 13:09
  • @sha_script_ui the reason why the function returns undefined it's because it never resolves. It just creates objects and adds event listeners. But those event handlers will return to their callers and it will be the browser thread. You need to invert the control and resolve the promise and that's what I tried to stress in my comment above – Diego D Feb 01 '23 at 13:10
  • @DiegoD the function `getHeightWidth` doesn't return `undefined`, it returns a `Promise` that immedately resolves (of course with a value of `undefined`) – derpirscher Feb 01 '23 at 13:11
  • @derpirscher ok your words are more exact than mine indeed. I agree. But yet the point is that such promise returned by the function is never resolved while a way I see to make it work is binding such promise to the event listeners so that they will take care of resolving it. I'm just using different words to say the same thing I commented since the beginning.. am I wrong? – Diego D Feb 01 '23 at 13:13
  • @DiegoD of course the promise is resolved. It's even resolved before any of the events are fired ... But it resolves to a value of `undefined`. – derpirscher Feb 01 '23 at 13:16

2 Answers2

1

I would break this into five separate functions:

  1. loadImageFromFile ~ a Promise that loads by filename using a FileReader
  2. loadImage ~ a Promise that sets the src of an Image object
  3. loadImageFromFile ~ combines the two above functions into one call
  4. isValidSize ~ validates the width/height of a given image
  5. previewFile ~ an event handler for listening to a file input

const previewFile = async () => {
  const preview = document.querySelector('img');
  const file = document.querySelector('input[type=file]').files[0];
  const image = await loadImageFromFile(file);
  const isValid = isValidSize(image);
  console.log(`Image size valid: ${isValid }`);
  preview.src = image.src; // Update preview
};

const loadImageFromFile = async (file) => {
  const reader = await readFile(file);
  return loadImage(reader.result);
};

const readFile = async (file) =>
  new Promise((resolve, reject) => {
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.addEventListener('load', (e) => resolve(fileReader));
    fileReader.addEventListener('error', (e) => reject(e));
  });

const loadImage = async (imageData) =>
  new Promise((resolve, reject) => {
    const image = new Image();
    image.src = imageData;
    image.addEventListener('load', function (e) { resolve(this);  });
    image.addEventListener('error', function (e) { reject(e);  });
  });

const isValidSize = (image) => {
  const { height, width } = image;
  return width === 100 && height === 100;
};
<input type="file" onchange="previewFile()" /><br />
<img src="" height="100" alt="Image preview" />

If you want one giant monolithic function, you can try:

Note: You are using this to only check if the image is valid. You will need to load the image again, if you want to do more with the image. This is why the first example is a better choice. It separates validation from the loading.

const preview = document.querySelector('img');

const previewFile = async () => {
  const preview = document.querySelector('img');
  const file = document.querySelector('input[type=file]').files[0];
  const isValid = await isValidSize(file);
  console.log(`Image size valid: ${isValid }`);
};

const isValidSize = async (file, expectedWidth = 100, expectedHeight = 100) =>
  new Promise((resolve, reject) => {
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.addEventListener('load', (e) => {
      const image = new Image();
      image.src = fileReader.result;
      image.addEventListener('load', function (e) {
        const { height, width } = this;
        resolve(width === expectedWidth && height === expectedHeight);
      });
      image.addEventListener('error', function (e) { reject(false);  });
      preview.src = image.src; // This is now coupled...
    });
    fileReader.addEventListener('error', (e) => reject(false));
  });
<input type="file" onchange="previewFile()" /><br />
<img src="" height="100" alt="Image preview" />
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
1

If you want to use async/await, you will want to wrap the events inside a Promise constructor.

Below is a simple working example, select a image file and it will give you the size.

function getFileSize(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = e => {
      const image = new Image();
      image.onload = e => resolve([image.width, image.height]);
      image.onerror = e => reject(e);
      image.src = reader.result;
    }
    reader.onerror = e => reject(e);
    reader.readAsDataURL(file);
  });
}


document.querySelector('input').addEventListener('change', async e => {
  const span = document.querySelectorAll('span');
  const size = await getFileSize(e.target.files[0]);
  span[0].innerText = size[0];
  span[1].innerText = size[1];
});
span {
  font-weight: bold;
}
<input type="file" accept="image/png, image/gif, image/jpeg"/>

<div>width: <span></span></div>
<div>height: <span></span></div>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
Keith
  • 22,005
  • 2
  • 27
  • 44