0

So what I am trying to accomplish is the page should show each character and film they were in. Below the character names, instead of the [object Promise], it should show the films they were in. But when I pass the function get_movies(films) to p tag I get [object Promise].

const fetch_data = async () => {
  axios
    .get("https://swapi.dev/api/people")
    .then((res) => {
       people(res.data.results);
     
    })
    .catch((err) => {
      console.log("Ooops!There is an error with fetching the data", err);
 
    });
};

let people = (data) => {
  data.map((person) => {
   //create films tags
    films = person.films;    
    
    let p = document.createElement('p');
    let linkText = document.createTextNode( get_movies(films));
    p.appendChild(linkText);
 
    document.body.appendChild(p);

      //create names with their info 
    let mainContainer = document.getElementById("myData");
    
    let div = document.createElement("div");
    div.innerHTML = "Name: " + person.name + " Hair Color:  " + person.hair_color + "  Birth Year:" + "  DOB: " +  person.birth_year;   
    mainContainer.appendChild(div);
    mainContainer.appendChild(p); 

   });
};
 
 
//fetch api from films array 
let get_movies = async (movies)=>{
    return movies.map(movie => {   
    axios
    .get(movie)
    .then((res) => {
        newRes = JSON.stringify(res)
        return showMovies(res)
    })

    })
}

// show each movie from the api array 
let showMovies = (movies =>{
    let eachMovie = movies.data.title
    return eachMovie
  
    })
 
fetch_data();

https://jsfiddle.net/Lqsnm9jv/

Aki
  • 3
  • 3

1 Answers1

0

Hey there and welcome to StackOverflow!

The async keyword in JS only applies to the function it's on. If you use closures (for example in a .map call, or call other functions, those will need to handle Promises themselves or be async, too.

const fetch_data = () => {
  return axios
    .get("https://swapi.dev/api/people")
    .then(res => {
      // This return is important!
      return people(res.data.results);
    })
};

Be aware though, that you need to use async functions or use promises in the other functions that fetch data, too. Therefore I return the people function and change it to be an async function.

You mixed between async functions and .then usage. Usually it's good to stick to using await in async functions. I attached a cleaned up code sandbox at the end.

Side note: a common practice to start an asynchronous task in JS is, to attach the .catch directly to the top-level function call:

fetch_data()
  .catch((err) => {
    console.log("Ooops!There is an error with fetching the data", err);
  });

As I said above, your people function has to be async, so it can fetch data. Since get_movies is async and is called inside the closure in .map, I made the closure async and used Promise.all to make sure fetching the movies for each person runs as expected. Crucially the await get_movies(films) is the important part. An async function returns a promise. To get to the underlying value you want to display, you have to await the result of the get_movies(films) call.

const people = async (data) => {
  await Promise.all(data.map(async (person) => {
   //create films tags
    films = person.films;    
    
    let p = document.createElement('p');
    let linkText = document.createTextNode(await get_movies(films));
    p.appendChild(linkText);
 
    document.body.appendChild(p);

      //create names with their info 
    let mainContainer = document.getElementById("myData");
    
    let div = document.createElement("div");
    div.innerHTML = "Name: " + person.name + " Hair Color:  " + person.hair_color + "  Birth Year:" + "  DOB: " +  person.birth_year;   
    mainContainer.appendChild(div);
    mainContainer.appendChild(p); 

   }));
};

Because we .map to create promises, the data fetching happens in parallel. Since the individual calls to axios.get(movie) and therefore get_movies(films) can resolve in any order, the order of the UI elements will also be random. To solve this, fetch the data first, and await all of the results. Then in the next step, iterate over the data to create the nodes.

const people = async (data) => {
  const personData = await Promise.all(data.map(async (person) => {
   //create films tags
    films = person.films;

    return { ...person, films: await get_movies(films) };
  }))
    
  personData.forEach((person) => {
    let p = document.createElement('p');
    let linkText = document.createTextNode(person.films);
    p.appendChild(linkText);
 
    document.body.appendChild(p);

      //create names with their info 
    let mainContainer = document.getElementById("myData");
    
    let div = document.createElement("div");
    div.innerHTML = "Name: " + person.name + " Hair Color:  " + person.hair_color + "  Birth Year:" + "  DOB: " +  person.birth_year;   
    mainContainer.appendChild(div);
    mainContainer.appendChild(p); 

   });
};

With return { ...person, films: await get_movies(films) }; I create a copy of the person object, overwriting films with the result of the fetched films. I do this because we need the properties of person as well as the fetched films in the next step.

The Promise.all ensures that all network requests have finished. And since we used .map the data is in the same order as in the original array.

The get_movies function uses .map but doesn't return anything. If you use curly braces ({) there is no implicit return value, you need to use return explicitly. Just as before you're returning a promise from axios in the .map, which means you get an array of promises. With Promise.all you wait for all promises / network requests to resolve and then convert it to an array of values.

//fetch api from films array 
let get_movies = async (movies) => {
  return await Promise.all(movies.map(movie => {
    return axios
      .get(movie)
      .then((res) => {
        newRes = JSON.stringify(res)
        return showMovies(res)
      })
  }))
}

I took the liberty of cleaning this up a little and putting it in a code sandbox.

Hope this helps. If you have any more questions please don't hesitate to ask.

Moritz Mahringer
  • 1,056
  • 14
  • 27
  • Hi Moritz, thank you so much for the detailed explanation and for providing the code sandbox. I really appreciate it. One more question, since the films arrays from api has also additional information such as realease date and list of characters that appear in specific film. Is this possible to display that information on a different html page? Do I need to create another div and after receiving this data, show that film name along with some details about that film such as the release date on films.html file? Thanks – Aki Jun 23 '21 at 14:27
  • That sounds like a new question. It's unclear what you mean by "different html page". If it's a different page you need to fetch the data again. Please open a follow up question with details on what you want to do, and I'll look into it. If you liked my detailed explanation I'd appreciate you'd give it an upvote, too. :) – Moritz Mahringer Jun 23 '21 at 18:33
  • I will, thanks again! I did upvote but because my reputation is below 15 it did not let my upvote! I wish I could! – Aki Jun 24 '21 at 18:44
  • here is my post https://stackoverflow.com/questions/68122518/how-to-display-results-of-api-on-new-page – Aki Jun 24 '21 at 20:40