0

I'm getting a headache from trying to come up with a solution, and I hope that someone here can solve my problem.

I am trying to create a program that will do a loop for a number of audio files, using NodeJS:

  1. Access audio file from source.
  2. Send audio file to outside API for treatment (this part is already handled).
  3. Obtain the answer to the audio file treatment
  4. Display the answer

The API cannot work on 2 or more audios at the same time because it mixes them up and returns incorrect results, so the audio list has to be handled sequentially (so, it does steps 1-5 for audio_1, then steps 1-5 for audio_2, etcetera).

I can do the loop just fine, but due to how Promises work, what the program actually does is this:

  1. Do step 1 for audio_1.
  2. Get a Promise to do step 2 for audio_1.
  3. Do step 1 for audio_2.
  4. Get a Promise to do step 2 for audio_2...
  5. Once it gets all the Promises, it listens in until all the the Promises are resolved.

And this messes things up due to the above mentioned problem.

Here's the code I have right now:

async function myFunction(client){
  ...
  const fileC = database()
  const vars = {"variable":"my_variable"}
  const url = "my_url"

  let result
  await fileC.find({}).forEach(f=>fileList.push(f))
  for (const f of fileList){
    await https.get(f.file_location,function(res){
      let form = new FormData()
      Object.keys(vars).forEach(key=>form.append(key,vars[key]))
      form.append("file",res,f.file_name)
      axios.post(url,form,{headers:form.getHeaders(),maxBodyLength:Infinity}).then((response)=>{
        console.log(response.data.val)
      })
    })
  }
}

My only obstacle is ensuring that the for loop will not move to one cycle until the previous one has finished - once I get that, then my full program should work.

Any ideas?

EDIT: modified a bunch of things because I realized I could skip them.

EDIT 2: just to point out. The objective is that the for loop will not go to do one cycle until the previous one is finished. I tried to do this using a while loop to block the cycle, but it didn't work, either.

  • Does this answer your question? [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – crashmstr Sep 13 '21 at 14:59
  • Short answer: use `for...of` instead of `Array.prototype.forEach` – crashmstr Sep 13 '21 at 15:00
  • Sorry, I added just that, but it still doesn't work – MikelDeepLearner Sep 13 '21 at 15:10
  • Why are you mixing `http.get` and `axios.post`? Axios has promise support. – crashmstr Sep 13 '21 at 17:28
  • Also, you still show this: `await fileC.find({}).forEach(f=>fileList.push(f))`. Is the `await` binding where you think it does? – crashmstr Sep 13 '21 at 17:29
  • I use https.get to download the file, it has nothing to do with promises - and in fact putting up promises there would defeat the actual objective, which is to prevent the system from starting the process for one audio before the previous one is done. As for that line with await, I need to use it so the program doesn't crash - I only use it to access a database that contains the data on the files I am working with. – MikelDeepLearner Sep 14 '21 at 06:44
  • 1
    Unless you promisfy `http.get`, it does not return a promise and the `await` there is meaningless. That is the point of my question. If the await does not await, the `for...of` also does not wait on each iteration. – crashmstr Sep 14 '21 at 11:14

2 Answers2

2

The https.get function, according to its documentation, does not return a promise.

You can, however, wrap it inside of a function that does return a promise like the following with promiseHttpsGet

const promiseHttpsGet = (url) =>
  new Promise((resolve, reject) => {
    https
      .get(url, (res) => {
        resolve(res);
      })
      .on("error", (er) => {
        reject(er);
      });
  });

async function myFunction(client) {
  const fileC = database();
  const vars = { variable: "my_variable" };
  const url = "my_url";

  let result;

  await fileC.find({}).forEach((f) => fileList.push(f));

  for (const f of fileList) {
    // The helper function returns a promise so you can await that
    const res = await promiseHttpsGet(f.file_location);

    let form = new FormData();

    Object.keys(vars).forEach((key) => form.append(key, vars[key]));
    form.append("file", res, f.file_name);

    // You can await the axios response as well instead of using .then
    const axiosResponse = await axios.post(url, form, {
      headers: form.getHeaders(),
      maxBodyLength: Infinity,
    });

    console.log(response.data.val);
  }
}
Abir Taheer
  • 2,502
  • 3
  • 12
  • 34
  • My problem is not with https.get - that works just fine. What I need is so it will wait until it does everything in the callback before it continues doing the same with the rest. In fact, making it so the get returns a promise is not something I want (because it would create a promise for all the files and begin running them all). – MikelDeepLearner Sep 14 '21 at 06:19
  • 1
    @MikelDeepLearner I don't think you understand how promises work. If you need to wait for the http get to complete, and the completion is the execution of a callback, converting to a promise *for that one call* allows the caller to wait *on that one promise*. before moving on to other code. – crashmstr Sep 14 '21 at 11:19
  • 1
    @crashmstr OK, you made me realize what was wrong with that comment, and it works now. Thanks! – MikelDeepLearner Sep 14 '21 at 14:18
0

OK, I managed to come up with a solution, based on what @crashmstr pointed out about the use of promises. I separated the function for sending the audio from the rest and it worked.

async function getData(f,fileC){
  const vars = {"variable":"my_variable"}
  const url = "my_url"
  return new Promise(function(resolve,reject){
    https.get(f.file_location,function(res){
      let form = new FormData()
      Object.keys(vars).forEach(key=>form.append(key,vars[key]))
      form.append("file",res,f.file_name)
      axios.post(url,form,{headers:form.getHeaders(),maxBodyLength:Infinity}).then((response)=>{
        let value = response == "" //placeholder for actual work
        let update = {"res":response,"value":value}
        resolve(update)
      })
    })
  })
}

async function myFunction(client){
  const fileC = database()
  let result
  await fileC.find({}).forEach(f=>fileList.push(f))
  for (const f of fileList){
    result = await getData(f,fileC)
    await fileC.updateOne(f,{$set:result["update"]})
  }
}

I admit that my experience with promises is quite limited, but this does, at least, serve as a good lesson.