0

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?

6
  • The console.log prints undefined, not an array of undefined ? This seems really weird Commented Jan 4, 2023 at 17:56
  • 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. Commented Jan 4, 2023 at 18:11
  • 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, which you never use for anything. Commented Jan 4, 2023 at 18:12
  • I'm using the final results to get the correct counts based on the 2 filters. Are you saying in the for loop within main(), I should be storing a new array every time into a variable in that scope? Commented Jan 4, 2023 at 18:17
  • 2
    So the following changes made it successful: moving the declaration of anAccountsResults out of the array in main and using return within the recursive function instead of using await Commented Jan 4, 2023 at 19:57

1 Answer 1

2

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));
  }
}
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.