How to insert data from another API call into function - javascript

I am building a project website which uses the rawg api to fetch data about games and displays them on the screen. I have also implemented a search functionality. My function getGame is using the base url which is called API_URL. My problem is that the data returned by this link does not return description as one of its variables. It has other stuff like game id and game name etc but no description. I have checked the docs and it says that in order to get the details of a game i need to use a different fetch link which takes in id as a parameter. So i implemented another function called getDescription() which takes in an id from the games.forEach function. I am able to console log all the descriptions of the games that are being displayed. My question is how would i be able to insert the description into the dom inside my showGames function. My code is attached below. Thanks.
const API_URL = `https://api.rawg.io/api/games?key=${apiKey}&page=5&page_size=40`;
const SEARCH_API = `https://api.rawg.io/api/games?key=${apiKey}&search="`;
const form = document.getElementById('form');
const search = document.getElementById('search-game');
const main = document.getElementById('main');
getGames(API_URL)
async function getGames(url){
const request = await fetch(url)
const response = await request.json()
showGames(response.results)
}
async function getDescription(id){
const request = await fetch(`https://api.rawg.io/api/games/${id}?key=${apiKey}`)
const response = await request.json();
showDescription(response.description)
}
function showGames(games){
main.innerHTML = '';
games.forEach(game => {
const { background_image, name, released, rating_top, id } = game;
const gameEl = document.createElement('div');
const platform = game.parent_platforms.map(platform => platform.platform.name).join(', ');
getDescription(id)
gameEl.classList.add('card');
gameEl.innerHTML = `
<div class="card-top">
<img src="${background_image}" alt="" class="card-image">
</div>
<div class="card-bottom">
<div class="card-info">
<h1 class="game-name">${name}</h1>
<h3>Release date: ${released}</h3>
<p>Platforms: <span>${platform}</span></p>
<p>*****I WANT TO ENTER THE DESCRIPTION HERE*****</p>
</div>
<div class="card-rating">
<span class="${getColorByRating(rating_top)}">${rating_top}</span>
</div>
</div>
`;
main.appendChild(gameEl);
})
}
Edit
Here is my console log for response inside getDescription function
Console log
I was able to get my wanted output by adding async right after my forEach like this games.forEach(async game => {....})
Here is my working code
const API_URL = `https://api.rawg.io/api/games?key=${apiKey}&page=5&page_size=40`;
const SEARCH_API = `https://api.rawg.io/api/games?key=${apiKey}&search="`;
const form = document.getElementById('form');
const search = document.getElementById('search-game');
const main = document.getElementById('main');
getGames(API_URL)
async function getGames(url){
const request = await fetch(url)
const response = await request.json()
showGames(response.results)
}
async function getDescription(id){
const request = await fetch(`https://api.rawg.io/api/games/${id}?key=${apiKey}`)
const response = await request.json();
return response;
}
function showGames(games){
main.innerHTML = '';
games.forEach(async game => {
const { background_image, name, released, rating_top, id } = game;
const gameEl = document.createElement('div');
const platform = game.parent_platforms.map(platform => platform.platform.name).join(', ');
const response = await getDescription(id)
gameEl.classList.add('card');
gameEl.innerHTML = `
<div class="card-top">
<img src="${background_image}" alt="" class="card-image">
</div>
<div class="card-bottom">
<div class="card-info">
<h1 class="game-name">${name}</h1>
<h3>Release date: ${released}</h3>
<p>Platforms: <span>${platform}</span></p>
<p>${response.description}</p>
</div>
<div class="card-rating">
<span class="${getColorByRating(rating_top)}">${rating_top}</span>
</div>
</div>
`;
main.appendChild(gameEl);
})
}

As you said that getDescription function just returns a string, so you can do something like this
async function getDescription(id){
const request = await fetch(`https://api.rawg.io/api/games/${id}?key=${apiKey}`)
const response = await request.json();
//showDescription(response.description)
//instead of using a function, just return the response from this func
return response
}
function showGames(games){
main.innerHTML = '';
games.forEach(game => {
const { background_image, name, released, rating_top, id } = game;
const gameEl = document.createElement('div');
const platform = game.parent_platforms.map(platform => platform.platform.name).join(', ');
//save response into a variable
const response = getDescription(id)
gameEl.classList.add('card');
gameEl.innerHTML = `
<div class="card-top">
<img src="${background_image}" alt="" class="card-image">
</div>
<div class="card-bottom">
<div class="card-info">
<h1 class="game-name">${name}</h1>
<h3>Release date: ${released}</h3>
<p>Platforms: <span>${platform}</span></p>
<p>${response.description}</p>
</div>
<div class="card-rating">
<span class="${getColorByRating(rating_top)}">${rating_top}</span>
</div>
</div>
`;
main.appendChild(gameEl);
})
}
Then use that variable response.description in the html string literal

You should be able to save the response of showDescription in a variable and then include it in your inner Html.
What it struggle with this is the fact that if array.forEach has a callback that returns a promise (like the async showDescription()), the javascript engine will not wait it to be resolved or rejected, so console will show an error that description is null.
There is a nice question where you can find how to loop promises here
You have to update getDescription like this:
async function getDescription(id){
const request = await fetch(`https://api.rawg.io/api/games/${id}?key=${apiKey}`)
const response = await request.json();
return response.description;
}
edit showGames like this
async function showGames(games) {
...
and use for inside showGames
for (const game of games) {
const description = await getDescription(game.id)
...
}
Every loop will wait getDescription() to be completed before start the next loop

Related

Know how many promise are pending

My problem is the following :
I have an array of ids that I need to map to some database Ids using an HTTP request to my API like HTTP GET /foo/{id}
I need to wait for all values to show the data in my application
I'm currently doing this the following
async getValuesByIds({}, ids){
const valuesPromise = ids.map(async (id) => this.$axios.$get(`/foo/${id}`))
return Promise.all(valuesPromise)
}
And in the code before printing the values :
this.loading = true
this.getLeadsValuesByIds(idsArray).then(data => {
this.loading = false
this.values = data
})
The code is working fine but takes some times to run if i have a lot of ids.
In general, the first request ended in about 0.5 seconds and depending on the number of request, the last one can go up to 4 to 5 seconds
My goal here is to display a loading text informating the user how many request are left and how many are done.
Here is a short example using the jsonPlaceHolder API.
Basically what i want to have is instead of loading.. The number of request left (like {n} / 99 loaded
const loadData = () => {
const ids = Array.from({length: 99}, (_, i) => i + 1)
updateDataText('loading....')
const dataPromise = ids.map(async(id) => {
const post = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
return post.data
})
Promise.all(dataPromise).then(res => {
updateDataText(JSON.stringify(res))
})
}
const updateDataText = (text) => {
const div = document.getElementById('dataText')
div.innerText = text
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
<button onclick="loadData()">LoadData</button>
<p id="dataText"></p>
Note : I'm using Nuxt, i don't know if that change something.
You could use a <progress> tag like this
I've also added inflight and finished counter if you prefer that
const loadData = () => {
const ids = Array.from({length: 99}, (_, i) => i + 1)
let nStarted = 0, nFinished=0;
const inflight = document.getElementById('inflight');
const finished = document.getElementById('finished');
const progress = document.getElementById('progress');
progress.max = ids.length;
updateDataText('loading....')
const dataPromise = ids.map(async(id) => {
nStarted++;
inflight.textContent = nStarted - nFinished;
const post = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
progress.value++;
nFinished++;
finished.textContent = nFinished;
inflight.textContent = nStarted - nFinished;
return post.data
})
Promise.all(dataPromise).then(res => {
updateDataText(JSON.stringify(res, null, 4))
})
}
const updateDataText = (text) => {
const div = document.getElementById('dataText')
div.innerText = text
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
<button onclick="loadData()">LoadData</button>
<progress id="progress" value=0></progress>
<div>In Flight: <span id="inflight"></span></span>
<div>Finished: <span id="finished"></span></span>
<pre id="dataText"></pre>

How to loop through multiple arrays within objects in a JSON API using JavaScript

I have an API I'm trying to fetch and display data from. The data I want to access is in the 'equipments' array as shown below.
I'm trying to loop through the 'equipments' array for each item but I'm having trouble displaying the data. I believe I'm either not accessing it properly, or not including another loop somewhere.
Here's what I have so far:
// Fetch Data
function getData() {
fetch(url)
.then((res) => res.json())
.then((data) => {
let output = "";
data.groups.equipments.forEach(function(product) {
output += `
<div class="card">
<img src=${product.photos.text} alt=${product.model} />
<h3>${product.year} ${product.manufacturer} ${product.model}</h3>
<p>${product.hours} hours</p>
View Details
</div>
`;
});
dataOutput.innerHTML = output;
})
}
getData();
Any idea what I need to do in order to get this working?
Since data.groups is an array, you first have to iterate through them before accessing the equipments.
You can also iterate through them using a forEach, so that's easy!
data.groups.forEach(function(group) {
group.equipments.forEach(product) {
output += `
<div class="card">
<img src=${product.photos.text} alt=${product.model} />
<h3>${product.year} ${product.manufacturer} ${product.model}</h3>
<p>${product.hours} hours</p>
View Details
</div>`;
}
})
instead of groups.equipments use
groups.map((g) => g.equipments)
You are trying to get the equipments property of the array. The property is in the object which is inside the array.
data.groups.forEach(obj => {
//obj.equipments is what you want now. It returns the equipments property of current object in the loop
})
You have to make twice forEach. Check here https://js.do/code/621480
Data groups is array also, so you have to take foreach group then foreach product in equipments.
const loadApi = async () => {
let url = await fetch(url)
let res = await url.json();
for (const element of res.groups) {
for (const element2 of element.equipments) {
getItemsFromProducts(element2);
}
}
}; loadApi();
const getItemsFromProducts = (data) => {
let demo = document.getElementById('demo');
demo.innerHTML = `
<div id="product">
<img src=${data.photos[0].text} alt=${data.model} />
<h3>${data.year} ${data.manufacturer} ${data.model}</h3>
<p>${data.hours} hours</p>
</div>
`;
}

How do I use the variable out of its asynchronous function scope

I need to use the variable that I declared inside the asynchronous function out of that scope. Is that possible?
movies.forEach((movie) => {
const { title, poster_path, vote_average, overview, release_date, id } = movie
async function getCredits(url) {
const res = await fetch(url)
const data = await res.json()
const directors = [];
const directorsName = directors.join(", ") // The one I want to bring out of its scope
}
const CREDITS_URL = `the credits url goes here and it uses this -> ${id}`
getCredits(CREDITS_URL)
const directorsName = directors.join(", ") // Like this
const card = document.createElement("div")
card.innerHTML = `
<div class="director">
<h3>Directed by ${directorsName}</h3> // This is where I need the variable
</div>
`
cards.appendChild(card)
})
It's possible to return something from your async function. You can move your getCredits function out of the loop, and make the loop async, something like:
async function getCredits(url) {
const res = await fetch(url)
const data = await res.json()
const directors = [];
// Do something with data?
return directors.join(", ");
}
movies.forEach(async (movie) => {
const { id } = movie
const CREDITS_URL = `the movie url goes here and it uses this -> ${id}`
const response = await getCredits(CREDITS_URL);
const card = document.createElement("div")
card.innerHTML = `
<div class="director">
<h3>Directed by ${response}</h3> // This is where I need the variable
</div>
`
cards.appendChild(card)
});
If you'd like to have the DOM be created more quickly and have a sort of "lazy loading" of directors' names, you could do something like this:
movies.forEach((movie) => {
const { title, poster_path, vote_average, overview, release_date, id } = movie;
const card = document.createElement('div');
card.innerHTML = `<div class="director">
<h3>Directed by <span class='dname'>...</span></h3>
</div>`;
const dname = card.querySelector('.dname');
async function getCredits(url) {
const res = await fetch(url);
const data = await res.json();
const directors = [];
dname.textContent = directors.join(', ');
}
const CREDITS_URL = `the credits url goes here and it uses this -> ${id}`;
getCredits(CREDITS_URL);
cards.appendChild(card);
});
Here you're creating the div and appending it immediately, but with a ... for directors' names. But you put that part in a span and then set the textContent of the span once getCredits resolves.
*Edit: This also has the added side-benefit of preventing HTML injection from your director's return, by not inserting them using innerHTML.

Creating a covid-19 app and im getting issues with the API being dynamic

Im working on a Coronavirus application. And im using this API: https://api.covid19api.com/live/country/south-africa
In my application a user is supposed to type the name of any given country and it should display the numbers of deaths, confirmed cases, recovered people. I keep getting a 404 error.
HTML & CSS can be found here:
https://github.com/Kazim786/coronavirus-updates/blob/master/index.html
https://github.com/Kazim786/coronavirus-updates/blob/master/style.css
(havent done much styling yet)
Here is my Javascript code:
// The api: https://api.covid19api.com/live/country/south-africa
// const theCountries = await axios.get(`https://api.covid19api.com/live/country/south-africa`)
// console.log(theCountries.data[0].Confirmed)
// console.log(theCountries.data[0].Deaths)
// console.log(theCountries.data[0].Recovered)
const countries = document.getElementById('countries').value
const results = document.getElementsByClassName('results')
const submitBtn = document.getElementById('submit')
const data = []
console.log(countries)
//Async Function
//`https://api.covid19api.com/live/country/${countries}`
console.log(byCountries())
async function byCountries(){
try {
const theCountries = await axios.get(`https://api.covid19api.com/live/country/${countries}`)
const deaths = theCountries.data[0].Deaths
const confirmed = theCountries.data[0].Confirmed
const recovered = theCountries.data[0].Recovered
data.push(deaths, confirmed, recovered)
console.log(theCountries)
await console.log(data)
}
catch{
console.log("Error");
}
}
//Show results function:
function showResults(){
if (countries !== null){
results.innerHTML = `${countries} has number of deaths, confirmed cases, recovery as the following: ${data}. `
} else {
results.innerHTML = 'Enter a name of a Valid Country'
}
}
//Add Event Listener
submitBtn.addEventListener('click', showResults)
You're trying to get the result while the input field is empty as js loads.
You can make the submit button call both byCountries() and showResults(), and you'll also need to fetch the value inside byCountries()
submitBtn.addEventListener('click', () => {
byCountries()
showResults()
})
async function byCountries(){
try {
const countries = document.getElementById('countries').value
const theCountries = await axios.get(`https://api.covid19api.com/live/country/${countries}`)
...

Query JSON-based API with user input with just javascript

I've been watching tutorials on using JSON data and JS, decided to work with an API and make a simple APP. I ran into a snag and I'm not sure what's causing the issue. The issue is around the way I'm using user input to modify the query string. When I make my endpoint something static and get rid of the 'movieSearch' function,like this:
const movies = [];
const endpoint = 'http://www.omdbapi.com/?apikey=myAPIkey=batman';
fetch(endpoint)
.then(blob => blob.json())
.then(data => movies.push(...data.Search));
It works as desired, granted it's static.
My current code is:
const movies = [];
function movieSearch() {
const replace = this.value;
const endpoint = 'http://www.omdbapi.com/?apikey=myAPIkey=' + replace;
movies.length = 0;
fetch(endpoint)
.then(blob => blob.json())
.then(data => movies.push(...data.Search));
}
function findMatches(wordToMatch, movies) {
return movies.filter(film => {
const regex = new RegExp(wordToMatch, 'gi');
return film.Title.match(regex) || film.Year.match(regex)
})
}
function displayMatches() {
const matchArray = findMatches(this.value, movies);
const html = matchArray.map(film => {
const regex = new RegExp(this.value, 'gi');
const titleName = film.Title.replace(regex, `<span class="hl">${this.value}</span>`)
const yearName = film.Year.replace(regex, `<span class="hl">${this.value}</span>`)
return `
<li>
<span class="name">${titleName}, ${yearName}</span>
<span class="population">${film.imdbID}</span>
</li>
`;
}).join('');
suggestions.innerHTML = html;
}
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');
searchInput.addEventListener('keyup', displayMatches);
searchInput.addEventListener('change', displayMatches);
searchInput.addEventListener('keyup', movieSearch);
The displayMatches function starts acting funny and sometimes returns the list items and other times doesn't. I can't figure out what's causing it. Whichever way I call my endpoint my movies array looks the same, so I'm thoroughly confused.
Any suggestions? Is there a better way to do this?
My HTML is fairly simple right now:
<form class="search-form">
<input type="text" class="search" placeholder="Movies">
<ul class="suggestions">
<li>test1</li>
<li>test2</li>
</ul>
</form>
Thanks!
(I'm trying to do this all in JS)
Edit:
An example of the JSON data when searching batman with the API:
{"Search":[{"Title":"Batman Begins","Year":"2005","imdbID":"tt0372784","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BZmUwNGU2ZmItMmRiNC00MjhlLTg5YWUtODMyNzkxODYzMmZlXkEyXkFqcGdeQXVyNTIzOTk5ODM#._V1_SX300.jpg"},{"Title":"Batman v Superman: Dawn of Justice","Year":"2016","imdbID":"tt2975590","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BYThjYzcyYzItNTVjNy00NDk0LTgwMWQtYjMwNmNlNWJhMzMyXkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg"},{"Title":"Batman","Year":"1989","imdbID":"tt0096895","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMTYwNjAyODIyMF5BMl5BanBnXkFtZTYwNDMwMDk2._V1_SX300.jpg"},{"Title":"Batman Returns","Year":"1992","imdbID":"tt0103776","Type":"movie","Poster":"https://ia.media-imdb.com/images/M/MV5BOGZmYzVkMmItM2NiOS00MDI3LWI4ZWQtMTg0YWZkODRkMmViXkEyXkFqcGdeQXVyODY0NzcxNw##._V1_SX300.jpg"},{"Title":"Batman Forever","Year":"1995","imdbID":"tt0112462","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BNWY3M2I0YzItNzA1ZS00MzE3LThlYTEtMTg2YjNiOTYzODQ1XkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg"},{"Title":"Batman & Robin","Year":"1997","imdbID":"tt0118688","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMGQ5YTM1NmMtYmIxYy00N2VmLWJhZTYtN2EwYTY3MWFhOTczXkEyXkFqcGdeQXVyNTA2NTI0MTY#._V1_SX300.jpg"},{"Title":"The Lego Batman Movie","Year":"2017","imdbID":"tt4116284","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMTcyNTEyOTY0M15BMl5BanBnXkFtZTgwOTAyNzU3MDI#._V1_SX300.jpg"},{"Title":"Batman: The Animated Series","Year":"1992–1995","imdbID":"tt0103359","Type":"series","Poster":"https://m.media-amazon.com/images/M/MV5BNzI5OWU0MjYtMmMwZi00YTRiLTljMDAtODQ0ZGYxMDljN2E0XkEyXkFqcGdeQXVyNTA4NzY1MzY#._V1_SX300.jpg"},{"Title":"Batman: Under the Red Hood","Year":"2010","imdbID":"tt1569923","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BYTdlODI0YTYtNjk5ZS00YzZjLTllZjktYmYzNWM4NmI5MmMxXkEyXkFqcGdeQXVyNTA4NzY1MzY#._V1_SX300.jpg"},{"Title":"Batman: The Dark Knight Returns, Part 1","Year":"2012","imdbID":"tt2313197","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMzIxMDkxNDM2M15BMl5BanBnXkFtZTcwMDA5ODY1OQ##._V1_SX300.jpg"}],"totalResults":"344","Response":"True"}
Issues causing this behavior:
The movieSearch function is async and might not update the data in time.
The API sometimes return an error.
This solved by the code below, note that I moved everything into the fetch resolver making sure the search only executes when the API has responded.
Here is a JS Bin: https://jsbin.com/kicesivigu/1/edit?html,js,output
function findMatches(wordToMatch, movies) {
return movies.filter(film => {
console.log(film.Title, wordToMatch);
console.log(film.Title.toLowerCase().includes(wordToMatch));
return film.Title.toLowerCase().includes(wordToMatch) || film.Year.toLowerCase().includes(wordToMatch);
});
}
function displayMatches(movies, value) {
const matchArray = findMatches(value.toLowerCase(), movies);
const html = matchArray.map(film => {
const regex = new RegExp(value, 'gi');
const titleName = film.Title.replace(regex, `<span class="hl">${value}</span>`);
const yearName = film.Year.replace(regex, `<span class="hl">${value}</span>`);
return `
<li>
<span class="name">${titleName}, ${yearName}</span>
<span class="population">${film.imdbID}</span>
</li>
`;
}).join('');
suggestions.innerHTML = html;
}
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');
searchInput.addEventListener('keyup', () => {
const endpoint = 'https://www.omdbapi.com/?apikey=63f88e02&s=' + searchInput.value;
fetch(endpoint)
.then(blob => blob.json())
.then(data => {
console.log('response from API');
console.log(data);
if (!data.Error) displayMatches(data.Search, searchInput.value);
});
});

Categories

Resources