Search code examples
javascriptnode.jsnext.jsasync-await

unable to await for promise in loop to prevent stall request


This code is api endpoint of a Next.js webapp, which fetch all the github repos, names of Repos and number of contributers. but problem is that when i use Promise.all the call doesnt return any thing (stall responce), and inside console.log() i can see populated array. and otherwise if i dont use Promise.all and useupdate array inside the forEach or Map loop i returns array with number of Repos but all the objects are empty.

try {
  const { public: publicRepositories, private: privateRepositories , allRepos : repositories} = await getRepositories(accessToken);

  const publicContributions = []
  const privateContributions = []

  const publicPromises = await Promise.all(publicRepositories.map(async repo => await getContributors(repo.owner.login, repo.name, accessToken)))
  
  console.log('publicPromise: ', publicPromises)
  
  const privatePromises = await Promise.all(privateRepositories.map(async repo => await getContributors(repo.owner.login, repo.name, accessToken)))


  const totalPublicContributions = publicPromises.reduce((sum, repoInfo) => sum + repoInfo[0].contributors, 0);
  const totalPrivateContributions = privatePromises.reduce((sum, repoInfo) => sum + repoInfo[0].contributors, 0);

  const totalContributions = totalPublicContributions + totalPrivateContributions;
  const accountName = publicRepositories.length > 0 ? publicRepositories[0].owner.login : privateRepositories[0].owner.login;

   res.status(200).json({
    accountName,
    privateRepoCount: privateRepositories.length,
    publicRepoCount: publicRepositories.length,
    totalContributions,
    publicPromises
  });
} catch (err) {
  console.error(err);
  res.status(500).send('An error occurred while fetching the repositories');
}

getRepositories returns the Privarte/Public repos arrays, but when i fetch getContributors() with forEach it return 28 empty objects in array (means we have 28 public repos but dont have any data).

    const publicPromises= publicRepositories.forEach(async repo => {
    const contributors = await getContributors(repo.owner.login, repo.name, accessToken);
    console.log('totalPublicContributions:', contributors)
  });

here is the getContributors function

   function getContributors(owner, repo, accessToken, type) {
    return new Promise((resolve, reject) => {
      const options = {
        hostname: 'api.github.com',
        path: `/repos/${owner}/${repo}/contributors`,
        headers: {
          'User-Agent': 'pixls.io',
          'Authorization': `token ${accessToken}`
        }
      };
  
      const req = https.request(options, res => {
        let data = '';
        res.on('data', chunk => {
          data += chunk;
        });
        res.on('end', () => {
          if (res.statusCode && res.statusCode !== 200) {
            console.log('res.statusCode', res.statusCode)
            console.log("Error...contributors...."); 
          } else {
            try {
              const contributors = JSON.parse(data);
              console.log('contributors', contributors.length)
              
              repoInfo.push({
                'name': repo,
                'privacy' : type,
                'contributors': contributors.length
              })
              
              resolve({
                'name': repo,
                'privacy' : type,
                'contributors': contributors.length
              });
            } catch (error) {
              console.log("Error...skipping 2...."); 
            }
          }
        });
      });
  
      req.on('error', error => {
        console.error(`Error making GitHub API request: ${error}`);
        reject(error);
      });
  
      req.end();
    });
  }

how to await for responce so that i can send populated 'publicPromises' in res.json


Solution

  • One thing you need to fix is to resolve or reject your promise in all code paths of getContributors(). You have a couple code paths that only log and never fulfill the promise, one when testing the status and one if JSON.parse() throws. When a promise involved in a Promise.all() never resolves/rejects, then await Promise.all() will never resolve and your code will get "stuck" waiting for a promise that never finishes.

    You can fix it like this:

       function getContributors(owner, repo, accessToken, type) {
        return new Promise((resolve, reject) => {
          const options = {
            hostname: 'api.github.com',
            path: `/repos/${owner}/${repo}/contributors`,
            headers: {
              'User-Agent': 'pixls.io',
              'Authorization': `token ${accessToken}`
            }
          };
      
          const req = https.request(options, res => {
            let data = '';
            res.on('data', chunk => {
              data += chunk;
            });
            res.on('end', () => {
              if (res.statusCode && res.statusCode !== 200) {
                console.log('res.statusCode', res.statusCode)
                console.log("Error...contributors...."); 
                // ==== Add next line ===
                reject(new Error(`statusCode was ${res.statusCode}`));
              } else {
                try {
                  const contributors = JSON.parse(data);
                  console.log('contributors', contributors.length)
                  
                  repoInfo.push({
                    'name': repo,
                    'privacy' : type,
                    'contributors': contributors.length
                  })
                  
                  resolve({
                    'name': repo,
                    'privacy' : type,
                    'contributors': contributors.length
                  });
                } catch (error) {
                  console.log("Error...skipping 2...."); 
                  // ==== Add next line ===
                  reject(error);
                }
              }
            });
          });
      
          req.on('error', error => {
            console.error(`Error making GitHub API request: ${error}`);
            reject(error);
          });
      
          req.end();
        });
      }
    

    If you want to get all the responses, even if some reject, then you can use await Promise.allSettled(...) instead of await Promise.all(...) and then iterate the results to see which ones were successful and which weren't.