-1

I have a set of forms relating to images (each form is used to add details to a respective image fetched from a MySQL database). The backend is handled with PHP, but the details from these images are sent to the database using the javascript Fetch API.

What I want do is show a button to 'upload more images' when the current list of forms has been submitted and the relevant data added to the database (i.e. no more images to process).

I'm using a for of loop so I can use async/await, but I can't get it to play ball.

In the code below I've made the processImageFormats() function async but I can't work out how to make the if statement after item.remove() "await" until the fetch() has happened? And is that the correct sequence of events that needs to happen?

Each time an image form is processed/submitted to the database the item.remove() removes that form element from the document.

I tried looking at this question Using async/await with a forEach loop amongst others and thus changed it to a for of loop from a forEach loop, but can't work out how to incorporate the await part to solve the problem at hand.

JavaScript

var imageForms = document.querySelectorAll('.image-upload-details-form');

var uploadMoreImages = document.querySelector('#upload-images-link');

async function processImageForms() {

    for (let item of imageForms) {
        
        item.addEventListener("submit", function (evt) {
        
            evt.preventDefault();

            const formData = new FormData(this);

            fetch("upload-details.php", {
                method: 'post',
                body: formData
            }).then(function(response){
                return response.text();
            }).then(function(text){
                console.log(text);
            }).catch(function (error){
                console.error(error);
            })

            item.remove();

            // ** show or hide 'upload more images' link when there are no more forms to process - the bit that isn't working **
            if (item.length === 0) {
                uploadMoreImages.style.display = 'inline-block';
            }

        })

    }
    
}

processImageForms();


// show or hide 'upload more images' link on pageload
if (imageForms.length === 0) {
    uploadMoreImages.style.display = 'inline-block';
} else {
    uploadMoreImages.style.display = 'none';
}

HTML

<a id="upload-images-link" href="upload.php">UPLOAD MORE IMAGES</a>
pjk_ok
  • 618
  • 7
  • 35
  • 90

3 Answers3

2

In this case it's not so much a question of using a for loop to be able to await, because regardless of the loop structure you're still inside of a callback function:

item.addEventListener("submit", function (evt) {

To await anything in the function it first needs to be async:

item.addEventListener("submit", async function (evt) {

This whole chain resolves to a Promise:

fetch("upload-details.php", {
    method: 'post',
    body: formData
}).then(function(response){
    return response.text();
}).then(function(text){
    console.log(text);
}).catch(function (error){
    console.error(error);
});

You can await the whole thing:

await fetch("upload-details.php", {
    method: 'post',
    body: formData
}).then(function(response){
    return response.text();
}).then(function(text){
    console.log(text);
}).catch(function (error){
    console.error(error);
});

Though I imagine it's cleaner and more consistent to await each step. Including the catch, the whole thing might look like this:

try {
    let response = await fetch("upload-details.php", {
        method: 'post',
        body: formData
    });
    let text = await response.text();
    console.log(text);
} catch (error) {
    console.error(error);
}
David
  • 208,112
  • 36
  • 198
  • 279
  • Hi @David. It still isn't workinng but I'm wondering if I need to do something with the `item.length === 0` code block now it's wrapped in async function ? – pjk_ok Aug 17 '21 at 14:51
  • @paul_cambs: You shouldn't need to, a comparison operation isn't asynchronous. What specifically isn't working? – David Aug 17 '21 at 14:51
  • The button doesn't appear when all the images have been processed (unless the page is refershed). I'm not getting any errors in the console though. – pjk_ok Aug 17 '21 at 14:59
2

Make the event handler function an async function and then await the calls to the fetch and res.text

item.addEventListener("submit", async function (evt) {
                             /* ^^^^^  make the event handler async */
     ...

     try {
        const response = await fetch("upload-details.php", {
            method: 'post',
            body: formData
        });

        const data = await response.text();
     } catch (error) {
        // handle error
     }

     ...
})

Event handler needs to be async because that's the function inside which you will use the await keyword.

processImageForms can be a non-async function because you are not directly using the await keyword inside it.


Note: Instead of adding a separate event listener on each item, I would consider taking advantage of event bubbling and add a single event handler on the common parent element of all items.

Yousaf
  • 27,861
  • 6
  • 44
  • 69
0

The problem turned out not to be async/await - although it was great to get the answers because they helped my understanding of this paradigm.

It turned out that I needed to re-assign the uploadMoreImages variable after item.remove() which I did to a new variable curImageForms

item.remove();

// update imageForms variable
var curImageForms = document.querySelectorAll('.image-upload-details-form')

// show or hide 'upload more images' link on pageload
if (curImageForms.length === 0) {
    uploadMoreImages.style.display = 'inline-block';
}

The uploadMoreImages was still using the .length property assigned prior to the fetch.

pjk_ok
  • 618
  • 7
  • 35
  • 90