Home > Software engineering >  How to delay a function until after a forEach statement completes?
How to delay a function until after a forEach statement completes?

Time:05-01

I am currently working on a messaging application using React. At the moment, I am struggling with determining a method of delaying the main function that posts a message to the chat.

The main issue is working with the Cloudinary API, for picture message submission, I need the urls that are spit out from the POST request.Thus, I also need the postMessage method to be delayed until after my photos have been uploaded to Cloudinary Database, and the 'temp' array has collected the new generated image urls. My issue begins and most likely ends with the fact that I cannot determine the proper way to delay postMessage until my forEach statement completes.

Here is the code for my handleSubmit Method:

const handleSubmit = async (event) => {
    event.preventDefault();

    const formElements = event.currentTarget.elements;
    const formData = new FormData();
    let temp = [];

    if (imagesSelected.length > 0) {
      imagesSelected.forEach((image) => {
        formData.append('file', image);
        formData.append('upload_preset', 'sendpics');
        
        instance.post('https://api.cloudinary.com/v1_1/dg7d5il8f/image/upload', formData )
        .then((res) => {
          temp.push(res.data.url);
        })
        .catch((err) => {
          console.log(err);
        });
      });

      const reqBody = {
        text: formElements.text.value,
        recipientId: otherUser.id,
        conversationId,
        sender: conversationId ? null : user,
        attachments: temp,
      };
          
      postMessage(reqBody);
    } else {
      const reqBody = {
        text: formElements.text.value,
        recipientId: otherUser.id,
        conversationId,
        sender: conversationId ? null : user,
        attachments: [],
      };
          
      postMessage(reqBody);
    }

    setText('');
    setImagesSelected(() => []);
  };

The issue remains in the first if statement, as once the forEach statement triggers, postMessage triggers right after, before temp even has a chance to be populated with the new url values. Apologies if the whole question itself is convoluted by any means. Please comment if I need to clarify anything about my question. Thanks for any help/suggestions!

CodePudding user response:

I think you'll need to await them with for-of loop using-async-await-with-a-foreach-loop
An example with your case:

for (const image of imagesSelected) {
  //...
  try {
    const resp = await instance.post(
      "https://api.cloudinary.com/v1_1/dg7d5il8f/image/upload",
      formData
    );
    temp.push(resp.data.url);
  } catch (error) {
    console.log(error);
  }
}

Another approach is replacing forEach with map and adding Promise.all As E. Shcherbo mentioned in comments

CodePudding user response:

Unfortunately, you cannot go with the standard Array.forEach(callback) method if each element needs to be run asynchronously because following code will not wait for the loop to finish.

A suitable method would be to create a Promise for each of the array elements and wait for those to resolve. If you have few elements, you can do that by hand, or if they are too much or simply not hardcoded, you can create that Promise-array with a loop.

Maybe like so:


let promiseArray = []

imagesSelected.forEach(item => {
  let p = new Promise((resolve, reject) => {
    // do your asynchronous stuff here
    instance.post(...

      // when you want the code to be "marked" as finished, run
      resolve(optionalReturnData)

      // when an error happens
      reject(optionalReason)
    ...
  })

  promiseArray.append(p)
})

// wait for the promises
Promise.all(imagesSelected).then(returnDataArray => {
  // now all promises have resolved and you can continue
  ...
  postMessage(...)
})

For more details, refer to the mdn documentation.

  • Related