JavaScript Asynchronous Programming with JavaScript Understanding Promises Handle Multiple Promises with Promise.all

Brendan Moran
Brendan Moran
13,743 Points

A good generic fix for the name-spelling mismatch bug.

Here's what I did to deal with any names where the two APIs disagree on the spelling. No spell-checking and correcting which needs to be updated every time an astronaut changes, just some client-friendly error handling. The app still functions and if there is a disagreement on spelling between the API's, this code will give the name (from astrosURL) to the user, an explanation that their info couldn't be retrieved from wikipedia, and a link to a google search. This only requires a few small changes to Guil's working code.

const astrosUrl = 'http://api.open-notify.org/astros.json';
const wikiUrl = 'https://en.wikipedia.org/api/rest_v1/page/summary/';
const peopleList = document.getElementById('people');
const btn = document.querySelector('button');

First, we need to let getJSON take an optional name parameter.

function getJSON(url, name = "") {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

In getJSON, the call to xhr.open will look a little different, depending on if we got the name parameter or not:

    if (name) {
      xhr.open('GET', `${url}${name}`);
    } else {
      xhr.open('GET', url);
    }
    xhr.onload = () => {
      if (xhr.status === 200) {
        let data = JSON.parse(xhr.responseText);
        //console.log(data);
        resolve(data);

Our error handling here needs to be very specific to wikipedia, and the type of error we get back when there is a disagreement on the spelling of the name (404). We're going to reject because this is going to make our lives easier with the error handling later on in the generateHTML function, but you could also resolve and pass in the same object for the value, you would just need to change your tests in generateHTML. More on that below. We need to give some good info for the rejection reason, because we're going to use this info in our error-handling HTML.

      } else if (url.includes('wikipedia') && xhr.status === 404) {
        reject({
          title: name,
          search: `https://www.google.com/search?q=${name}`
        });
      } else {
        reject(new Error(xhr.status));
      }
    };
    xhr.onerror = () => reject(Error('A network error occurred.'))
    xhr.send();
  });
}

Important: use Promise.allSettled(). It allows a promise to reject, and you can still work with the data. It's not all or nothing.

function getWikiProfiles(data) {
  const profiles = data.people.map(person => {
    return getJSON(wikiUrl, person.name);
  });
  return Promise.allSettled(profiles);
}

In the generateHTML function, we are going to test for fulfillment or rejection (or, if you resolved, you'll have to test for the presence of some other property). Be sure to log the data and look at how it differs from the promise.All() data. The data we want for the HTML is now nested in a parent object, so I reassign the person parameter to more easily access the nested data. (Again: log the data to console and take a look at it if there's any confusion here.) We're going to use the data passed as the reason for the rejection to create an HTML section that helps the user find more info on the astronaut that we couldn't get from wikipedia.

function generateHTML(data) {
  console.log(data);

  function createSection() {
    const section = document.createElement('section');
    peopleList.appendChild(section);
    return section;
  }
  data.forEach(person => {
    if (person.status === 'fulfilled') {
      person = person.value;
      const section = createSection();
      section.innerHTML = `
        <img src=${person.thumbnail.source}>
        <h2>${person.title}</h2>
        <p>${person.description}</p>
        <p>${person.extract}</p>
      `;
    } else {
      person = person.reason;
      const section = createSection();
      section.innerHTML = `
        <h2>${person.title}</h2>
        <p>Couldn't retrieve profile for ${person.title} from Wikipedia.</p>
        <h3><a href=${encodeURI(person.search)} target="_blank">Search Google for more information on ${person.title}</p></h3>
      `;
    }
  });

}

btn.addEventListener('click', (event) => {
  event.target.textContent = 'Loading...'
  getJSON(astrosUrl)
    .then(getWikiProfiles)
    .then(generateHTML)
    .catch(err => {
      let html = `
        <h3>Something went wrong.</h3>
      `;
      peopleList.insertAdjacentHTML('afterbegin', html);
      console.log(err);
    })
    .finally(() => event.target.remove());
});