JavaScript Asynchronous Programming with JavaScript Asynchronous JavaScript with Callbacks Implement a Callback

I cannot wrap my head around this code chunk at all

btn.addEventListener('click',()=> {
                     getJSON(astrosUrl, (json)=>{
                     json.people.map(person=>{
                     getJSON(wikiUrl + person.name);
    });

  });

}); 

I truly have no idea what is going on here in this code chunk. These nested functions and callbacks are really confusing me. What is the json argument ? Where does json.people.map come from? What is person? How do we know person has a name property? Really need some clarification since I don't feel the previous lessons were sufficient to grasp this.

3 Answers

Cameron Childres
MOD
Cameron Childres
Treehouse Moderator 11,545 Points

Hey vg8,

I'm still wrapping my head around this myself, it was a tough one for sure! I'll see if I can help but don't take my words as gospel as this is still new to me. If anyone reading this can explain things in a more concise or clear way, please chime in!

TL;DR:

  • What is the json argument? The data returned from getJSON() on astrosUrl
  • Where does json.people.map come from? The data from getJSON() has a people property which contains an array of objects. The map() method iterates over array items to generate a new array.
  • What is person? person is a parameter for the callback in map(), representing the current array item being processed
  • How do we know person has a name property? person is an object in the array from the people property in the astrosUrl data, this object has a name property. Add console.log(data) before the return statement in getJSON() to view it in your console

I'm going to walk through how I see the function running step by step as that's what's helped me to make sense of it, maybe it will help you too and hopefully it won't confuse you any further. I'm working off the code in the "final" folder of the project files:

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');

// Make an AJAX request
function getJSON(url, callback) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = () => {
    if(xhr.status === 200) {
      let data = JSON.parse(xhr.responseText);
      console.table(data);
      return callback(data);
    }
  };
  xhr.send();
}

// Generate the markup for each profile
function generateHTML(data) {
  const section = document.createElement('section');
  peopleList.appendChild(section);
  section.innerHTML = `
    <img src=${data.thumbnail.source}>
    <h2>${data.title}</h2>
    <p>${data.description}</p>
    <p>${data.extract}</p>
  `;
}

btn.addEventListener('click', (event) => { 
  getJSON(astrosUrl, (json) => {
    json.people.map( person => {
      getJSON(wikiUrl + person.name, generateHTML);
    });
  });
  event.target.remove();
});

When the button is clicked run getJSON(), passing astrosUrl and an arrow function as arguments.
getJSON() runs, sends the request, turns the responseText from the request in to JSON, and stores it in data. Then it returns callback(data). Since we passed an arrow function as callback, this arrow function is what returns with data as its argument.

The arrow function with json as its parameter runs. data was passed as the argument, so json is the contents of data. If you console.log(data) in getJSON() you'll see an object like this when using astrosUrl:

{message: "success", number: 7, people: Array(7)}
message: "success"
number: 7
people: Array(7)
  0: {craft: "ISS", name: "Sergey Ryzhikov"}
  1: {craft: "ISS", name: "Kate Rubins"}
  2: {craft: "ISS", name: "Sergey Kud-Sverchkov"}
  3: {craft: "ISS", name: "Mike Hopkins"}
  4: {craft: "ISS", name: "Victor Glover"}
  5: {craft: "ISS", name: "Shannon Walker"}
  6: {craft: "ISS", name: "Soichi Noguchi"}

This is how we know to use people as a property of json.

json.people.map() iterates over each item in the array in people. This is the function it runs:

(person) => {
      getJSON(wikiUrl + person.name, generateHTML);
    }

The parameter person represents each array item being iterated over. Looking at the array above the items are objects with two properties: craft, and name. So when we say person.name we're accessing the name property of an object in the array.

getJSON() is run on each iteration but with two new arguments. This time we're passing it wikiUrl with person.name concatenated on it with generateHTML() as the callback function. getJSON() does its magic and returns new data (JSON parsed responseText for each astronaut's name) from the new URLs, but this time passes data as an argument to generateHTML.

generateHTML() runs with this new data. This time the data object is much bigger, you can view the whole thing by adding console.log(data) in getJSON(). I'm going to post just the relevant parts for one iteration:

description: "Russian engineer and cosmonaut"
extract: "Sergey Vladimirovich Kud-Sverchkov was born on August 23, 1983 at the Baikonur Cosmodrome in the Kazakh Soviet Socialist Republic. Sergey Kud-Sverchkov is married and father of one daughter and one son. Since April 2010, he is a Russian Cosmonaut of the Russian Space Agency Roscosmos. He is currently in space."
thumbnail: {source: "https://upload.wikimedia.org/wikipedia/commons/thu…/2/2b/Jsc2020e032664.jpg/213px-Jsc2020e032664.jpg", width: 213, height: 320}
title: "Sergey Kud-Sverchkov"

From here you can see how generateHTML() has title, description, extract, and thumbnail.source properties to access. It then proceeds to use these values to generate the markup that is added to the page.

I hope this helps. If something isn't making sense feel free to let me know and I'll see if I can clarify.

Thank you so much! It really did help clarify some things for me. One more question though - suppose I was looking at someone else's code to make sense of it - how do you go about understanding the flow in the nested statement :

btn.addEventListener('click', (event) => { 
  getJSON(astrosUrl, (json) => {
    json.people.map( person => {
      getJSON(wikiUrl + person.name, generateHTML);
    });
  });

Like you mentioned in your answer you worked through the function "step by step" . So did you start from the parent function addEventListener and then look at its arguments and work your way down/inwards or do you start with the bottom arguments in getJSON function and work your way up/outwards? I feel like I understand this example now after having read your answer and mulling it over but if I came across something similar I wouldn't know how to approach understanding it.

Cameron Childres
Cameron Childres
Treehouse Moderator 11,545 Points

I worked from the top down, looking at what the parent function does to give context to what's inside. It went like:

  1. Wait for a click, on that click run getJSON
  2. Look at getJSON. It takes two arguments, a url and a callback function. It produces JSON data from the url and gives it to the callback function.
  3. Check out the callback. It accesses a property of that data and maps over it with getJSON with new arguments.
  4. Review getJSON again in the context of the new arguments. New data is fed to generateHTML.
  5. Look at generateHTML to see the result.

Then with the context in my head I worked backwards to make a story out of it: "HTML is generated with data from a wikiUrl that is made from data retrieved from astrosUrl. This is carried out on button click."

There was a fair bit of jumping back and forth in the process. Whenever I didn't understand where something was coming from I went back and logged relevant bits to the console.

So I guess my answer is "all of the above"? 😅
I don't know if it's the most efficient or best way to work through it, but it's worked for me so far.

Thanks everyone.Does any one of you know where i can access more information about callback functions and the whole jazz that was commented in the sections above?