-1

I'm working on a section of code where the user inputs an image using a , basically like what is used to change a profile picture. My problem is that when I try to read the file with a FileReader(), the program doesn't circle back to the onloadend before the functions that need the result are called. In fact, it only circles back after my fetch gets a response. I've tried async/await, callbacks as far as I know, and some sketchy roundabout methods, but nothing works so far.

My conversion function looks like this:

async function getB64StringFromFile(fileIn) {
    var reader = new FileReader();

    reader.onloadend = function () {
        return reader.result;
    }

    if (fileIn && (fileIn.type == "image/jpeg" || fileIn.type == "image/png" && fileIn.size < 1 * Math.pow(2, 20))) {
        await reader.readAsDataURL(fileIn);
    } else {
        console.log("bad");
    }
}

My main function looks somewhat like this:

async function updateBook(ev) {
    var form = ev.target.closest("form");
    var data = new FormData(form);

    /*long section of code that is irrelevant to the problem*/
    var objectData = Object.fromEntries(data);

    //the image as a file -- which already proved to be working
    let file = document.querySelector('input[type=file]#imageInput').files[0];

    if (file) {
        objectData.ImageB64String = await getB64StringFromFile(file);
    }

    fetch("/inventory/update/", {
        method: "POST",
        contentType: "application/json; charset=utf-8",
        body: JSON.stringify(objectData),
        headers: {
            "Content-Type": "application/json",
            "RequestVerificationToken": csrfToken,
        },
    }).then((res) => { ... })
}

The big problem here is that the firing order is

  1. UpdateBook()
  2. Call getB64StringFromFile()
  3. Fetch
  4. Response from controller -- which is bad because it didn't have the Base64String along with it
  5. Goes back to the onloadend and returns the String

What I would want is

  1. UpdateBook()
  2. Call getB64StringFromFile()
  3. wait for the onloadend to be ready
  4. Go back to the onloadend and get the result
  5. Fetch

I would love help, I've been at this for 9 hours. There's probably missing awaits or useless/misplaced ones, but I've tried so many things along the way that I forgot what is right.

Edit: I tried to use the PromiseApi but it still doesn't wait, instead it moves on as before while the promise is qualified as 'pending'.

new code with Promise:

function getB64StringFromFile(fileIn) {
    return new Promise(function (fulfilled, rejected) {
        var reader = new FileReader();
        var string;

        reader.onloadend = function () {
            string = reader.result;
            fulfilled(string); 
        }

        if (fileIn && (fileIn.type == "image/jpeg" || fileIn.type == "image/png" && fileIn.size < 1 * Math.pow(2, 20))) {
            reader.readAsDataURL(fileIn);
        } else {
            console.log("bad");
        }
    })
}

Maybe I'm not using Promise right, I don't know.

  • Your second version should work. – Kaiido Oct 28 '22 at 08:38
  • I haven't found a fix and will not be trying anything from now on since the project is completed. The closest thing to a fix I managed to make was separating the process in two distinc parts, so I don't need to wait for it. – MGC_StackOverflow Dec 07 '22 at 16:36

1 Answers1

0

Would this work instead of your current event handler?

reader.addEventListener("load", () => {
    fulfilled(reader.result);
  }, false);

Also, instead of console logging if fileIn is invalid, you should invoke rejected(). That would make more sense for the caller of getB64StringFromFile().

Ted Nyberg
  • 7,001
  • 7
  • 41
  • 72