Search code examples
javascriptasync-awaites6-promise

Confused about Promises and async-await


I'm making an application using the GitHub api and I'm having trouble with async functions. I'm new to using async so I would appreciate the help a lot. Here's the code I've written till now:

const getFiles = async function(token, reponame) {
  var gh = new GitHub({
    token: token
  });

  reponame = reponame.split("/");
  const repo = gh.getRepo(reponame[0], reponame[1]);

  let head = new Headers();
  head.append("Authorization: ", "token " + token);

  const getContents = new Promise((res, rej) => {
    repo.getContents(null, "content", true, (err, files) => {
      if (err) rej(err);
      else return files;
    }).then(files => {
      let promises = [
        files.map(
          file =>
            new Promise(res => {
              fetch(file.downloadURL).then(body => {
                res(body.text);
              });
            })
        )
      ];

      const retFiles = [];
      await Promise.all(promises.map(promise => retFiles.push(promise)));
      res(retFiles)
    });
  });

  return getContents;
};

The error I'm getting is unexpected reserved word at the line where I used await. Thanks in advance


Solution

  • There are many issues here. The code is very complicated; there's no need for all of these promises and layers of indirection and nesting to achieve what you need.

    The pattern you're trying to do is very common:

    • Make a request to get a list of entities (files, users, urls...).
    • For each entity returned in that list, make another request to get more information for it.
    • Return the result as a promise (it has to be a promise, since async functions can only return promises).

    The way to do this is to break the problem into stages. Use the await and async keywords instead of .then in most cases. To make the example reproducible, I'll use a scenario where we want to get user profiles for the most recent n gists created on GitHub--this is fundamentally equivalent to what you're doing and I'll leave it to you to extrapolate.

    The first step is to get the initial list of entities (recently created gists):

    const res = await fetch("https://api.github.com/gists/public");
    const gists = await res.json();
    

    Next, for each gist in our array of gists from 0..n, we need to fire off a request. It's important to ensure we're not serializing anything here using await:

    const requests = gists.slice(0, n).map(gist =>
      fetch(`https://api.github.com/users/${gist.owner.login}`)
    );
    

    Now that all the requests are in flight, we need to wait until they complete. This is where Promise.all comes in:

    const responses = await Promise.all(requests);
    

    Last step is to get the JSON from each response, which requires another Promise.all:

    return await Promise.all(responses.map(e => e.json()));
    

    This is our final result which can be returned. Here's the code:

    const getRecentGistCreators = async (n=1) => {
      try {
        const res = await fetch("https://api.github.com/gists/public");
        const gists = await res.json();
        const requests = gists.slice(0, n).map(gist =>
          fetch(`https://api.github.com/users/${gist.owner.login}`)
        );
        const responses = await Promise.all(requests);
        return await Promise.all(responses.map(e => e.json()));
      }
      catch (err) {
        throw err;
      }
    };
    
    (async () => {
      try {
        for (const user of await getRecentGistCreators(5)) {
          const elem = document.createElement("div");
          elem.textContent = user.name;
          document.body.appendChild(elem);
        }
      }
      catch (err) {
        throw err;
      }
    })();

    As a note of improvement, it'd be better to ask for JSON in the same stage as requests complete using an extra promise, but for simplicity's sake, we'll do it in two serial steps. As a design point, it's probably good to break the overburdened getRecentGistCreators into two separate steps as well.