2

I have a for loop in which there is a function call and that function further has some async code. Now the issue is that the for loop doesn't wait for the async code to return from the nested function call and continues iterating.

The function aFunctionThatRunsAsync comes from a library that I have no control over.

Below is all the code.

// arr.length = 2

for (let i in arr) {
  console.log(i, 'i1')

  functionOne(arr[i], i, (err, res) => {
    console.log(res)
  })
}

function functionOne(inp, idx, callback) {
  console.log(idx, 'i1')
  const processor = aFunctionThatRunsAsync(inp, err => {
    if (err) return callback(err);
  });

  processor.on('complete', data => {
    console.log(data, idx, 'i3')
    return callback(null, data)
  });
}

Problem:

The log looks like this after the execution of the code:

0 i1 // expected
0 i2 // expected
1 i1 // unexpected, expected to have logged: data(obj) i3
1 i2 // unexpected, expected to have logged: 1 i1

And finally logs:

data(obj) 0 i3
data(obj) 1 i3 // Not always in the same order

I want the for loop to wait for the async code to return/log and run synchronously in the correct order so the final output look like this:

0 i1
0 i2
data(obj) 0 i3
1 i1
1 i2
data(obj) 1 i3

m-ketan
  • 1,258
  • 2
  • 17
  • 23
  • `const processor = aFunctionThatReturnsAPromise(/*...*);` and then `processor.on('complete', ...` That doesn't look like a promise to me. – T.J. Crowder Mar 19 '20 at 11:39
  • Apologies if I have used incorrect terminologies. All I know is that this function works asynchronously. I have edited the question to convey the same. – m-ketan Mar 19 '20 at 11:42
  • 1
    Your problem is corresponding with this one described here: https://stackoverflow.com/questions/11488014/asynchronous-process-inside-a-javascript-for-loop – Mario Boss Mar 19 '20 at 11:46
  • _“Now the issue is that the for loop doesn't wait for the async code to return”_ - please don’t say you were surprised by that … that is what asynchronous _means_. – CBroe Mar 19 '20 at 11:46
  • I actually don't want the code to behave asynchronously at all, it's just a 3rd party library that's forcing that behaviour. – m-ketan Mar 19 '20 at 11:49

1 Answers1

2

That code doesn't have anything in the for-in to make it wait for those callbacks.

The code in functionOne doesn't look promise-based, so unless you want to change that code, you need to wait for the callback before starting the next iteration, something like this:

process(0);
function process(i) {
  if (i < arr.length) {
    console.log(i, 'i1')
    functionOne(arr[i], i, (err, res) => {
      if (err) {
        console.error(err);
        // I assume you don't want to continue here
      } else {
        console.log(res);
        process(i + 1);
      }
    });
  }
}

Alternatively, you could make functionOne return a promise:

function functionOne(inp, idx) {
  return new Promise((resolve, reject) {
    console.log(idx, 'i1')
    const processor = aFunctionThatReturnsAPromise(inp, err => {
      if (err) return reject(err);
    });

    processor.on('complete', data => {
      console.log(data, idx, 'i3');
      resolve(data);
    });
  })
}

Then:

let promise = Promise.resolve();
for (let i = 0; i < arr.length; ++i) {
    promise = promise.then(() => (
        functionOne(arr[i], i).then(res => {
            console.log(res);
        })
    ));
}
promise.catch(error => console.error(error));

Note that the let inside the for there is important. It can't be var, and it has to be inside the for. That's because we need the callback we pass to then to close over the i for that loop iteration, which it will with let as above but wouldn't with var.

It would be easier to use in an async function:

// In an `async` function
try {
    for (let i = 0; i < arr.length; ++i) {
        console.log(await functionOne(arr[i], i));
    }
} catch (error) {
    console.error(error);
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875