Avoid using await in a for loop when Promise result updates the iterating loop variable itself

54 Views Asked by At

Is there any way to not use await inside loop for following code?

const redirects = ['redirectId1'];

for (let i = 0; i < redirects.length; i++) {
  const promiseResult = await anAsyncFunction(redirects[i]);
  
  if (promiseResult.redirects) {
    redirects.forEach(newRedirect => {
      if (!redirects.includes(newRedirect)) redirects.push(newRedirect);
    });
  }
}

I've tried using Map

const redirects = ['redirectId1'];

const promiseArr = redirects.map(async redirect => {
  const promiseResult = await anAsyncFunction(redirect);

  if (promiseResult.redirects) {
    redirects.forEach(newRedirect => {
      if (!redirects.includes(newRedirect)) redirects.push(newRedirect);
    });
  }
});

await Promise.all(promiseArr);

but in doing so, code executes further before newRedirect is pushed to redirects array thus not calling anAsyncFunction for new added value.

I want to know if there any possible way to achieve this?

2

There are 2 best solutions below

0
On

If u are interested that each redirect batch will be in parallel and keep asking for more redirects as long as there is, u can achieve it as following :

const handleRedirects = async (_redirects) => {
  const newRedirects = [];
  await Promise.all(
    _redirects.map(async (redirect) => {
      const promiseResult = await anAsyncFunction(redirect);

      if (promiseResult.redirects) {
        promiseResult.redirects.forEach((newRedirect) => {
          if (!_redirects.includes(newRedirect)) newRedirects.push(newRedirect);
        });
      }
    })
  );
  return newRedirects;
};


const run = async () => {
  const redirects = ['redirectId1'];
  let newRedirects = [ ...redirects ];
  do {
    newRedirects = await handleRedirects(newRedirects);
    redirects.push(...newRedirects);
  } while (newRedirects.length)
}
1
On

If the idea is to send out each request as soon as possible, without waiting for earlier requests to resolve first, you can use a recursive async function that calls itself when the result contains redirects:

const recurse = (redirects) => Promise.all(
  redirects.map(r => anAsyncFunction(r)
    .then(({ redirects }) => {
      if (redirects) {
        return recurse(redirects);
      }
    })
  )
);
// pass in the initial array:
await recurse(redirects);

If you also need to use the populated outer redirects array later, then:

const recurse = (redirects) => Promise.all(
  redirects.map(r => anAsyncFunction(r)
    .then(({ redirects }) => {
      return redirects
        ? recurse(redirects).then(res => [redirects, res])
        : [];
    })
  )
);
// pass in the initial array:
const allRedirects = [initialRedirects, await recurse(initialRedirects)].flat(Infinity);