0

I have created a Google Apps Script web application for uploading images to my Google Drive. It has a lot of rows, each row has an input tag for uploading file. I created an only one submit button to upload all chosen images in one time. However, I would like each row to upload each image in order and then delete that row when it was uploaded successfully in order as well. The problem is I can't find the right way to use async/await function to upload the images to Drive with FileReader because when I run the script, It's still work as asynchronous function.

async function uploadImage() {
    var row   = document.getElementsByClassName('row');
    var file  = document.getElementsByClassName('img-file');
    var name  = document.getElementsByClassName('img-name');

    for (let i=0; i<row.length; i++) {
      var image = file[i].files[0];
      if (image) {
        var reader = new FileReader();
        reader.readAsDataURL(image);
        reader.onloadend = async (event) => { 
            await new Promise((resolve, reject) => { 
                google.script.run.withSuccessHandler(r => resolve())
                                 .uploadImgToDrive(name[i].value, event.target.result) 
            }).then(() => row[i].innerHTML='');
        }        
      }
    }    
}
6
  • What is the problem now? Is there a error? Commented Sep 4, 2019 at 14:58
  • @TheMaster the problem is each row was deleted not in order when it was successfully uploaded. Thank you. Commented Sep 4, 2019 at 15:01
  • 1
    But different images have different sizes. uploadImgToDrive might return earlier for image5, while image1 might finish later even though image1 started uploading before image5. Synchronizing or ordering it might take more time. Parallel operations are faster. Commented Sep 4, 2019 at 15:04
  • 1
    @TheMaster is right... But consider that it also depends on throughput, latency, API limitations, etc. I would suggest to to remove all rows and show a global loading bar or leave them disappear without order using cool animations. Commented Sep 4, 2019 at 15:19
  • @TheMaster Thank you for your advice. Commented Sep 4, 2019 at 15:37

2 Answers 2

3

If I understood what your goal is, the following code should work. Only one image at a time will be uploaded. Let me know if it fit your needs.

Please note that your function will always be asynchronous though because you have two asynchronous tasks inside it (FileReader and API call). The only thing you can decide is how many operations you want to handle "at the same time".

Finally, remember that anytime you use an async function it will immediately return an unresolved promise that will resolve with the value that the function returns when it finishes running.

Inside async functions, await is used to "wait" for a promise to resolve before continuing (in this case, the promise that you are creating with new Promise()), so it is similar to using .then() directly on the promise (you don't need both, that is why I removed the .then() part).


function uploadImages() {

    var row   = document.getElementsByClassName('row');
    var file  = document.getElementsByClassName('img-file');
    var name  = document.getElementsByClassName('img-name');

    (function nextImg(i) {
        var image = file[i].files[0];
        if (image) {
            var reader = new FileReader();
            reader.readAsDataURL(image);
            reader.onloadend = async (event) => {
                await new Promise((resolve, reject) => {
                    google.script.run.withSuccessHandler(r => resolve())
                        .uploadImgToDrive(name[i].value, event.target.result);
                });
                row[i].innerHTML='';
                if (i < row.length - 1) {
                  nextImg(i + 1);
                }
            };
        }
    })(0);

}

Optimised version (not tested):

Avoids using innerHTML (important) and tries to reuse FileReader() instance (not sure if it will work).


function uploadImages() {

    let row   = document.getElementsByClassName('row');
    let file  = document.getElementsByClassName('img-file');
    let name  = document.getElementsByClassName('img-name');

    let reader = new FileReader();

    (function nextImg(i) {
        if (file[i].files[0]) {
            reader.onloadend = async function onloadend(e) {
                await new Promise((resolve) => {
                    google.script.run.withSuccessHandler(r => resolve(r)).uploadImgToDrive(name[i].value, e.target.result);
                });
                while (row[i].firstChild) {
                    row[i].removeChild(row[i].firstChild);
                }
                if (i < row.length - 1) {
                    nextImg(i + 1);
                }
            };
            reader.readAsDataURL(file[i].files[0]);
        }
    })(0);

}

Sign up to request clarification or add additional context in comments.

2 Comments

@TheMaster you are right! I forgot the loop condition. Fixed, thanks!
Yes, my goal is only one image at a time to be uploaded but loop for all rows in order on the page. I really appreciated for your help.
0

Another way to do this would be to hook up the loadend event of reader to a new promise and chain it:

async function uploadImage() {
    var row   = document.getElementsByClassName('row');
    var file  = document.getElementsByClassName('img-file');
    var name  = document.getElementsByClassName('img-name');

    for (let i=0; i<row.length; i++) {
      var image = file[i].files[0];
      if (image) {
        var reader = new FileReader();
        reader.readAsDataURL(image);
        let promiseOfAllDone = new Promise(res=>reader.addEventListener('loadend',res))
            .then(event=>new Promise((resolve, reject) => { 
                    google.script.run.withSuccessHandler(resolve)
                                 .uploadImgToDrive(name[i].value, event.target.result)
             }).then(() => row[i].innerHTML='')
            .catch(e=>console.error(e));
        await promiseOfAllDone;//wait for all promises to be fulfilled
       }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.