0

Suppose, we want to check the width of an image before uploading it. Here is the fiddle. In the end we need to wait for the end of all the asynchronous operations. I tried to use Promises for this purpose. But, unfortunately, good_images are empty inside alert. Seems, that I'm missing something simple.

I tried Promise.all, but it also doesn't work. Fiddle.

$(document).ready(function () {
    function check_image_dimensions ($this, image, img_name) {

        let min_width = + $this.data('min_width');
        if (typeof(min_width) === "number" && image.width < min_width) {
            alert(`Error loading photo ${img_name}, image width: ${image.width}px is less then minimal: ${min_width}px.`);
            return false;
        }

        return true;
    }

    function unknown_error () {
        alert('Unknown error while loading image.');
    }

    $(document).on('change', '.upload_images', function() {
        var $this = $(this);
        let images = $this.prop('files');
        if (typeof(images) === 'undefined' || images.length === 0) {
            return false;
        }

        let good_images = Array();
        let promise = Promise.resolve();// <- I tried this, but it is wrong
        for (let i = 0; i < images.length; i++) {
            let promise = new Promise(function(resolve, reject) {
                const reader = new FileReader();
                reader.onload = resolve;
                reader.readAsDataURL(images[i]);
            });

            promise.then((event) => {
                const img  = new Image();
                img.onload = () => {
                    if(check_image_dimensions($this, img, images[i].name) === true) {
                        good_images.push(i);
                    }
                };

                img.onerror = unknown_error;
                img.src = event.target.result;
                return i;
            }).catch(unknown_error);
        }

        promise.then(() => alert(good_images));
    });
});
user4035
  • 22,508
  • 11
  • 59
  • 94
  • Your fiddle seems to bear little relation to the code you post here. I can though answer your question for the code you have above in the question. – Robin Zigmond Oct 08 '20 at 20:32
  • 2
    Hi, have a look at promises.all (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). – Chris Adams Oct 08 '20 at 20:32
  • @ChrisAdams I tried to push all the promises into an array and then run Promises.all, but it also gives an empty alert. Do you want me to post the code here? – user4035 Oct 08 '20 at 20:38
  • @RobinZigmond Ops, sorry, old version. Fiddle updated. – user4035 Oct 08 '20 at 20:39
  • 1
    Actually I've discovered that converting this to use `Promise.all` isn't as simple as I thought, because the array is only populated not just after all promises resolve, but after all the image `onload` events have fired. May I ask what the actual aim of this code is? Just getting an array of indices doesn't strike me as the end product you really desire. – Robin Zigmond Oct 08 '20 at 20:44
  • @RobinZigmond I want to make a validator of images before they are sent to server. – user4035 Oct 08 '20 at 20:46
  • @Keith How to get the sizes of images without FileReader? – user4035 Oct 08 '20 at 20:48
  • @user4035 Just noticed your doing it for uploads,. What I would do here is make both the FileReader part and the image.onload part into promises. – Keith Oct 08 '20 at 20:52

2 Answers2

1

If you want to process all of these images in parallel, you can create an array of promises and wait for them using Promise.all. Note that when processing your image, you will also need to wrap that part in a promise:

const good_images = [];
const promises = [];
for (let i = 0; i < images.length; I++) {
  const image = images[I];
  let promise = new Promise( ... );

  promise = promise.then((event) => {
    const img = new Image();
    img.src = event.target.result;

    return new Promise(resolve => {
      img.onload = () => {
        if (is_good(img)) {
          good_images.push(i);
        }
        resolve();
      };
    });
  })

  promise = promise.catch(() => { ... });

  promises.push(promise);
});

Promise.all(promises).then(() => alert(good_images));
Wex
  • 15,539
  • 10
  • 64
  • 107
  • `images.map(...)` won't work here, because `images` is a FileList, not an Array. – user4035 Oct 09 '20 at 12:59
  • Updated my answer to convert the `FileList` to an array. See also: https://stackoverflow.com/questions/25333488/why-isnt-the-filelist-object-an-array – Wex Oct 09 '20 at 14:57
  • Yay, it works. Is it possible to do it using good old for loop instead of map? I tried, but good_images were still empty. – user4035 Oct 09 '20 at 17:42
  • Sure. `FileList` should be iterable so that approach makes sense especially if you don't want to do the array conversion. Before your for loop, create an empty array of promises. Inside your for loop, push the promise we are currently returning to your promises array. If you get stuck and need me to update my answer, let me know. – Wex Oct 09 '20 at 17:49
  • I am stuck, tried to do it myself for 2 hours. Can you show the 2-nd solution with for loop? – user4035 Oct 09 '20 at 18:15
  • Updated my answer – Wex Oct 09 '20 at 22:09
  • The code with `for` doesn't increment `i` correctly, I tweacked it myself. But the code with `map` works. I can give you a fiddle if you want. – user4035 Oct 11 '20 at 20:18
  • In the future if you see a correctable problem feel free to edit it yourself. I updated my answer – Wex Oct 12 '20 at 15:20
0

the problem is that you're only calling the "then" without setting the variable again. Se the following example.

let arrdata = [1,2,3,4];
let prom = Promise.resolve();

arrdata.map(data => {

  // Here's the deal
  prom = prom.then(() => {
    console.log(data);
  });
});

prom.then(() => console.log('end'));
Mauricio Sipmann
  • 465
  • 6
  • 21