Home > other >  Calling recursive function in loop with async/await and Promise.all
Calling recursive function in loop with async/await and Promise.all

Time:01-06

I have a use case where I'm trying to loop through an array of objects, where I need to make some GraphQL requests that may have some pagination for a given object in the array. I'm trying to speed up performance by pushing the recursive function to an array of promises, and then use Promse.all to resolve all of those.

I'm running into an issue though where I'm getting an undefined response from Promise.all - The end goal is to have the following response for each unique object in the array:

[{
  account: test1,
  id: 1,
  high: 2039,
  critical: 4059
},
{
  account: test2,
  id: 2,
  high: 395,
  critical: 203
}]

...where I'm only returning anAccount object after recursion is done paginating/making all requests for a given account object.

Here is the sample code:

const fetch = require('isomorphic-fetch');
const API_KEY = '<key>';

async function main() {
  let promises = [];

  let accounts = [{'name': 'test1', 'id': 1}, {'name': 'test2' , 'id': 2}];

  for (const a of accounts) {
    let cursor = null;
    let anAccountsResults = [];
    promises.push(getCounts(a, anAccountsResults, cursor));
  }

  let allResults = await Promise.all(promises);
  console.log(allResults);
}

async function getCounts(acct, results, c) {
  var q = ``;

  if (c == null) {
    q = `{
        actor {
          account(id: ${acct.id}) {
            aiIssues {
              issues(filter: {states: ACTIVATED}) {
                issues {
                  issueId
                  priority
                }
                nextCursor
              }
            }
          }
        }
      }`
  } else {
    q = `{
        actor {
          account(id: ${acct.id}) {
            aiIssues {
              issues(filter: {states: ACTIVATED}, cursor: "${c}") {
                issues {
                  issueId
                  priority
                }
                nextCursor
              }
            }
          }
        }
      }`
  }

  const resp = await fetch('https://my.api.com/graphql', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'API-Key': API_KEY
    },
    body: JSON.stringify({
        query: q,
    variables: ''}),
  });

  let json_resp = await resp.json();

  let aSingleResult = json_resp.data.actor.account.aiIssues.issues.issues;
  let nextCursor = json_resp.data.actor.account.aiIssues.issues.nextCursor;
  console.log(nextCursor);

  if (nextCursor == null) {
    results = results.concat(aSingleResult);
  } else {
    results = results.concat(aSingleResult);
    await getCounts(acct, results, nextCursor);
  }

  let criticalCount = results.filter(i => i.priority == 'CRITICAL').length;
  let highCount = results.filter(i => i.priority == 'HIGH').length;

  let anAccount = {
        account: acct.name,
        id: acct.id,
        high: highCount,
        critical: criticalCount
      };

 return anAccount;
}

main();

logging anAccount in function getCounts has the correct detail, but when returning it, logging the output of Promise.all(promises) yields undefined. Is there a better way to handle this in a way where I can still asynchronously run multiple recursive functions in parallel within the loop with Promise.all?

CodePudding user response:

Your main problem appears to be that results = results.concat(aSingleResult); does not mutate the array you passed, but only reassigns the local variable results inside the function, so the anAccount only will use the aSingleResult from the current call. Instead of collecting things into a results array that you pass an a parameter, better have every call return a new array. Then in the recursive await getCounts(acct, results, nextCursor) call, do not ignore the return value.

async function main() {
  let promises = [];

  const accounts = [{'name': 'test1', 'id': 1}, {'name': 'test2' , 'id': 2}];

  const promises = accounts.map(async acct => {
    const results = await getIssues(acct);
    const criticalCount = results.filter(i => i.priority == 'CRITICAL').length;
    const highCount = results.filter(i => i.priority == 'HIGH').length;
    return {
      account: acct.name,
      id: acct.id,
      high: highCount,
      critical: criticalCount
    };
  });
  const allResults = await Promise.all(promises);
  console.log(allResults);
}


const query = `query ($accountId: ID!, $cursor: IssuesCursor) {
  actor {
    account(id: $accountId) {
      aiIssues {
        issues(filter: {states: ACTIVATED}, cursor: $cursor) {
          issues {
            issueId
            priority
          }
          nextCursor
        }
      }
    }
  }
}`;

async function getIssues(acct, cursor) {
  const resp = await fetch('https://my.api.com/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'API-Key': API_KEY
    },
    body: JSON.stringify({
      query: q,
      variables: {
        accountId: acct.id,
        cursor,
      }
    }),
  });
  if (!resp.ok) throw new Error(resp.statusText);
  const { data, error } = await resp.json();
  if (error) throw new Error('GraphQL error', {cause: error});

  const { nextCursor, issues } = data.actor.account.aiIssues.issues;
  if (nextCursor == null) {
    return issues;
  } else {
    return issues.concat(await getIssues(acct, nextCursor));
  }
}
  • Related