Search code examples
typescriptasync-awaiteslinttypescript-eslint

Async/Await not allowed in loops, TypeScript using Got package


I am using the got package and trying to send a GET request to check items in an array. This request is looped for each item in the array and then these items are further sorted into more arrays, the rest of the code depends on this list. Afterwards one of these arrays will be sent to another server for DELETE requests.

The structure is as follows:

for (let i in arrTags){
    const {body} = await got(`https://example.com/path/to/${i}`)
   
      if (body.length > 0 &&
          // find current revision (Git SHA) to compare with i
          body.current_revision.substring(0, tag.length) !== i) {
        arrTagsToDelete.push(tag)
      } else {
        arrTagsIgnored.push(tag)
      }
}

later on in the code we delete items in arrTagsToDelete:

for (let i in arrTagsToDelete){
    await got.delete(`https://anotherwebsite.com/path/to/${i}`, {username: args.username, password: args.password}) //using oclif we get auth
}

I am getting an issue with ESLint telling me I cannot use Async-await inside a for-loop, I know I cannot use a forEach since async-await does not play nicely according to other answers on StackOverflow,it will run the rest of the code before the GET request and DELETE requests are done. I have seen as well that disabling ESLint warnings for this scenario isn't uncommon.

I would prefer to maybe have a cleaner solution to this problem than disabling the ESLint, unless that is the only way.

Thank you in advance.


Solution

  • Using await inside a loop is generally considered bad-practice.

    This is because asynchronous functions are designed to run, well asynchronously. However, loops work serially - they process one item at a time.

    If you use await inside the loop - you can only make one async call per iteration - this means that you have to wait for one to finish before you send off the next one.

    This leads to a "waterfall", which will slow your code down.

    The fix is to parallelise your work (as @apokryfos mentioned):

    await Promise.all(arrTagsToDelete.map(
      (_, i) => got.delete(
        `https://anotherwebsite.com/path/to/${i}`,
        {username: args.username, password: args.password},
      ),
    )
    

    Or more verbosely:

    const promises = [];
    for (const i = 0; i < arrTagsToDelete.length; i += 1) {
      promises.push(
        got.delete(
          `https://anotherwebsite.com/path/to/${i}`,
          {username: args.username, password: args.password},
        )
      );
    }
    await Promise.all(promises);
    

    By not awaiting inside the loop, we are firing off all of our calls at once and allowing them to run asynchronously in parallel. After they're all in flight, we use Promise.all to wait until they are all finished.


    It's worth noting as an aside that there are some cases where you might want to process some asynchronous calls synchronously and serially.

    These cases are rare, and you should be doing this consciously and cautiously. If you have one of these cases, then use an eslint-disable comment.