fetching multiple urls from an api - javascript

Basically I am working with a star wars API called swapi (i haven't worked with APIs before) and I am fetching the data with a simple async-await function.
My goal is to get all the movies that API holds (a total of 6 of them) I am using a template tag in HTML and I'm cloning it in javascript and then displaying all 6 movies.
So to clarify, I am getting an array of 6 objects that I'm displaying the title of each movie in HTML without a problem since the title is in a string format, but every object has also a key (characters) that has a value of an array and inside that array is a big list of characters that play in the movie but they are in a URL format (http://swapi.dev/api/people/1/) except the last number in the URL changes for each of them.
Is there a smart way to fetch and display all the character names for each movie they have played in? because all the ways I'm trying aren't working. The ideal solution would be to display all the characters that have played in each movie and having the possibility to click on them and seeing a few details that they hold.

so this is my guess (I have no time to test this second, but will shortly and get this answer working don't pass.)
async function getSWdata(){
await fetch('starwars api')
.then(res=>res.json())
.then(data=>data.people.map(async (x)=>await fetch(x).then(res=>res.json())));
}
the things I'm unsure about are the exact syntax of async => functions, or any complications of async functions nested inside an async function...
but this might get you on the right track regardless.
EDIT:::
this below is working mostly for me (I am iffy sure on promises => there are improvements to be made)
async function getSWdata(){
return await fetch('https://swapi.dev/api/films')
.then(res=>res.json())
.then(data=>
{
const r = data.results.map((results)=>
{
const toons = results.characters.map(async (toon)=>await fetch(toon).then(res=>res.json()))
results.characters = toons;
return results;
})
data.results = r;
return data;
});
}
const SWData = getSWdata();
console.log(SWData);

Related

How to display all players names in the Ball Don't Lie API array

I am doing one of my first projects using the Ball Don't lie API, trying to build my version of an ESPN landing page. I am using https://www.balldontlie.io/api/v1/players. I am using Javascript, I have been stuck for days trying to understand how to display the first and last name of all of the players on the landing page in HTML. I only know how to display one name if I use data.data[0]. I've tried .map, loops, it's just not clicking. I want to be able to display other stats in the array as well. Can anyone help?
This my Javascript code:
async function getPlayers() {
const response = await fetch ('https://www.balldontlie.io/api/v1/players');
const data = await response.json();
const players = data.data;
console.log(players);
displayPlayer(players);
}
function displayPlayer(players) {
const scores = document.getElementById('scores');
scores.innerHTML = `
${players.first_name} ${players.last_name}`;
}
getPlayers()```
I had tried .map, I've tried loops, I am just not understanding what function is going to show the players. Maybe my orignal code doesn't make sense. I've tried watching Youtube and can't find anyone doing it in simple Javascript.
You can try this in your script and edit points 2. and 4. for better display of what you need to show
// 1. GET request using fetch()
fetch("https://www.balldontlie.io/api/v1/players")
// Converting received data to JSON
.then((response) => response.json())
.then((json) => {
// 2. Create a variable to store HTML table headers
let li = `<tr><th>ID</th><th>first_name</th><th>height_feet</th><th>height_inches</th> <th>last_name</th><th>position</th><th>im lazy...</th></tr>`;
// 3. Loop through each data and add a table row
console-console.log(json.data);
json.data.forEach((user) => {
li += `<tr>
<td>${user.id}</td>
<td>${user.first_name} </td>
<td>${user.height_feet}</td>
<td>${user.height_inches}</td>
<td>${user.last_name}</td>
<td>${user.position}</td>
<td>${user.team.id}</td>
<td>${user.team.abbreviation}</td>
<td>${user.team.city}</td>
<td>${user.team.conference}</td>
<td>${user.team.division}</td>
<td>${user.team.full_name}</td>
<td>${user.team.name}</td>
</tr>`;
});
// 4. DOM Display result
document.getElementById("users").innerHTML = li;
});
And your html body part look like this
<div>
<!-- Table to display fetched user data -->
<table id="users"></table>
</div>
Your constant players is an array. In order to access a player's information within that array, you would need to index each player to then access their object of key:value pairs.
That is why you can get the first player's name to show when you save players as data.data[0]. This is indicating that you want to access the object in position 0 in the array. If you wanted the second player's information you would reference data.data[1], and so forth.
With trying to keep as much of your original code as possible (and adding some comments), I believe this is what you were trying to achieve.
async function getPlayers() {
// Fetch the API and convert it to json.
const response = await fetch ('https://www.balldontlie.io/api/v1/players');
const data = await response.json();
// Save the returned data as an array.
const players = data.data;
console.log(players);
// Create an element to display each individual player's information.
players.array.forEach(player => {
displayPlayer(player);
});
}
function displayPlayer(player) {
// Grab the element encasing all players.
const scores = document.getElementById('scores');
// Create a new element for the individual player.
const playerContent = document.createElement('div');
// Add the player's name.
playerContent.innerHTML = `
${player.first_name} ${player.last_name}`;
// Add the player content into the encasing division.
scores.appendChild(playerContent);
}
getPlayers()
We will use the forEach() function to index each player's object in the array of data for us, grab your "scores" element you created on your HTML page, then we will "append" (add to the end) each player's information into your "scores" element.
The website link below has some useful information to read that can help you build on your existing code when you want to start adding styling.
https://www.thesitewizard.com/javascripts/insert-div-block-javascript.shtml
This site has some useful information on using "promises" when dealing with async functions that will come in handy as you progress in coding.
https://www.geeksforgeeks.org/why-we-use-then-method-in-javascript/
These website links were added as of 02/04/2023 (just to add as a disclaimer to the links because who knows what they will do in 2030 O.o).
Hope this helps!

My react component is failing due to component rendering before state is set. How can I resolve this issue?

I don't know if what I'm trying to do is possible but I've been trying for a week now and I feel like I'm so close but I just can't get there. Maybe it's not possible? Anyway, using reactjs and next js (I'm new to coding so I'll try my best to explain clearly what I'm trying to do):
I have a webapp that shows user transactions (from an api that I don't own/didn't create). I added like/ comment functionality to this app using firestore. Basically every time someone comments or likes a post their username and like/comment get's stored in my database along with the post id (which came originally from the api - which again - I didn't create). I thought it would be nice to let users know when someone likes or comments on their transaction and the way I planed to do this was like this :
First, query (my database - firestore) all posts that have a like or a comment, then set that post_ID in state
Next, I'll loop through all of those post_ID's and use them to complete a URL for an api fetch. You see, the api ends like the /sales/{saleID} .... which sale ID is the same as my post ID but the name varies because I changed it for my data base. That fetch will return a bunch of arrays , each of which show a seller/buyer.
Then I can take each buyer and seller and create a notification that says something like "hey {seller} {buyer}, someone liked your post. And I would only show this message if it's the current user and the current user matches the buyer or seller.
So part 3 I haven't done yet but I'm pretty sure I can pull off - I've done something similar. Part 1 seems to be working great! Part 2 is where I think I'm failing. I keep getting a "failed to fetch" but I think this is because some of the arrays are returning empty. Could you guys help me by reviewing my code and letting me know what you think? Like I said, I'm new, so I'm sure I wrote a lot of dumb stuff in there but this is a work in progress and a lot of stuff I did was me trying things out. Here is the code:
Also, here are some issues I've identified:
when I console.log(posts)- It returns an empty array twice, then it returns the array of postsID's but multiple times. Sometimes 3 times, sometimes 7 times. This number seems to vary though but 3 and 7 are what I keep seeing.
when I console.log(postID)- same idea
when I console.log(postData)- very similar. I get an empty array a couple of time, then I get an array for the post ID. Here is the strange thing. Ideally, this is supposed to contain an array of transaction info for each post ID that was used, but instead I get an empty array, then another, then my transaction arrays start coming in but a lot are duplicates.
-So seems to me that the issue is at the very beginning, the postID's being returned so many times.
I also found something else. If I comment everything out in the return and just put some dummy text, and then I console log postData (which is the most important item), then everything seems to work perfectly: I get an empty array a time or two then I get on array listed per post ID. So I'm thinking that the issue is that this component is failing because it's crashing before it's done rendering. So it's trying to return something that's not there yet (the postData.map....)
function UserNotifications() {
const [posts, setPosts] = useState([]);
const [postData, setPostData] = useState([]);
const postID = posts.map((post) => post.data().likePostID);
//------------ STEP 1: GET DATA FROM FIREBASE AND USE DATA ON API -------------------//
useEffect(() => {
onSnapshot(
query(collection(db, 'Feed Posts'), orderBy('timestamp', 'desc')),
(snapshot) => setPosts(snapshot.docs)
);
}, []);
useEffect(() => {
if (posts.length != 0) {
postID.map(async (likePostID) => {
const response = await fetch(
`https://proton.api.atomicassets.io/atomicmarket/v1/sales/${likePostID}`
);
const { data } = await response.json();
setPostData(data);
});
}
}, [posts]);
console.log(postData);
//----------------------------------- STEP 2: RETURN ----------------------------------//
return (
<>
{' '}
notifications coming soon
{/* {postData.map((result) => {
const { buyer, seller } = result;
if (seller || buyer) {
return <> hello {buyer}</>;
}
})} */}
</>
);
}
export default UserNotifications;
Rendering virtually always happens multiple times in React, and it is up to you to prevent issues caused by uninitialized data, which is the case here.
A simple way to do so is by adding another return, just above the return that you already have, as follows:
if (!postData)
return null;
You need to do this for all variables that can be empty (or otherwise invalid) at any time during rendering.

Dialogflow to firestore inline editor javascript: sorting and filtering on different parameters of collection

I have a database in Google cloud firestore with a collection of documents that each includes the following parameters:
country, headline, URL, date.
I need to return two separate things depending on the input in Dialogflow. The first thing is the headlines of the 3 latest news and the second thing is the headlines of the 3 latest news in France.
I am to do it in Dialogflows inline editor, which is javascript, but it doesn't seem entirely the same as javascript e.g. in Node.
I have already solved the first problem with the following code:
function TopNews_Global(agent) {
agent.add('Here are the latest Global news');
let documents = db.orderBy('Date', 'desc').limit(3);
return documents.get()
.then(snapshot => {
snapshot.forEach(doc => {
agent.add('-' + doc.data().Headline + '.');
agent.add('Link:(' + doc.data().URL + ')');
});
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
});
}
Where db is my collection in firebase.
The following code is my solution to the second thing I want to return, and this is where I am stuck. It is not possible to filter and order over two different fields as I do. But there MUST be a way around this - it's a pretty simple thing I wanna do.
https://firebase.google.com/docs/firestore/manage-data/export-import
function TopNews_France(agent) {
agent.add('Here are the latest French news');
let documents = db.orderBy('Date', 'desc').where('Country', '==', 'France').limit(3);
return documents.get()
.then(snapshot => {
snapshot.forEach(doc => {
agent.add('-' + doc.data().Headline + '.');
agent.add('Link:(' + doc.data().URL + ')');
});
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
});
}
Assuming that as you state, db is a collection object, your query is a valid one for Firestore.
db.orderBy('Date', 'desc').where('Country', '==', 'France').limit(3);
Firestore does allow queries across multiple fields, assuming that the only orderBy or range queries are on the same field. Thus, your query is valid (one orderBy and one non-range where on different fields).
You didn't mention what error you are getting, but I suspect it is this (nominally visible in a Javascript console):
The query requires an index. You can create it here: (url)
That is because this is a query which requires a composite index -- it sorts on one field and filters on another.
Thus, to fix this, you just need to create the index. In an ideal case, you can just click on the URL in that error message and be brought to the exact point in the Firebase console to create the exact index you need. If for some reason you need to create it manually, you can also do that through the console. After it completes building, your query should work.
In this case, you will need to create an index for whatever collection db is pointing at, with the fields Date (in descending order) and Country (in either descending or ascending order -- the equality won't care), and with collection scope.

JavaScript (node.js) How to get the URL of a random image from a subreddit?

Basically, I'm trying to get the URL of a random picture from a specific subreddit. I've tried using the raw JSON here, but I can't quite figure it out. I've been using snekfetch to get the JSON, since it's worked before on less complicated sites, but I've seen other methods like superagent and snoowrap that I don't have any idea how to use properly. Here's what I've tried using snekfetch (I'm trying to incorporate this into a discord bot):
case "pic":
if (!args[1]) return message.channel.send("Enter a title (Be VERY specific");
// pics was set equal to "https://www.reddit.com/r/pics.json" earlier
snekfetch.get(pics).then(r => {
let postTitle = args[1];
let img = r.preview.images[0].source.url;
let entry = r.find(post => post.title == postTitle);
let picture = new Discord.RichEmbed()
.setAuthor(entry.title)
.addField(entry)
.setImage(img);
message.channel.send(picture);
//message.channel.send(entry.preview.images[0].source.url);
});
break;
I'm new to JSON, so it wouldn't surprise me if this code is full of horrible mistakes.
Through some googling, I managed to find that apparently each reddit post has an ID in base 36. But these ID's aren't actually in order, so I'd need to store them all in an array and randomly select from that.
In short, how do I retrieve an image from reddit as a URL, and how do I put a certain amount of these images into an array?
Using the JSON data provided you can get all of the images using something like this:
async function getImages(url) {
const images = [];
const response = await snekfetch.get(url);
response.body.data.children.forEach((child) => {
child.data.preview.images.forEach((image) => {
images.push(image.source.url);
});
});
return images;
}
If you need to only gather N images, then you may want to use a standard for loop that breaks when N === images.length.

React-Redux Javascript app - trying to get lots of API calls to fire in order

I'm having an issue with successive API calls (using JQuery's AJAX) to two different APIs in order to build objects with certain attributes. Here's the summary of my app and what I'm trying to do:
The user enters in the name of an actor or director, and the app is meant to return a total of five movies, each of which has certain attributes like title, overview, year, budget, revenue, and a link to a YouTube preview. I'm using The Movie Database API, plus the YouTube API for the YouTube link.
Here's the order of how things currently to work, with all of this happening in the action creator of the Redux app:
Actor name gets sent to the TMDB API -- returns ActorID number
ActorID number gets sent to the TMDB API -- returns 20 movies with: title, overview, year, poster link, and MovieID number
For EACH movie in that list, the MovieID number gets sent to the API -- returns more attributes: budget, revenue, and IMDB-ID (to use in a link later)
Also for EACH movie in step 2, the title gets sent to the YouTube API -- returns a link to the preview.
Once all of this information is assembled for each movie, I want to return the first five movies and dispatch them as the action payload to the Redux store.
I'm using some promises, and I've tried everything I could think of in terms of rearranging the flow of functions, but I can't get all the information I need with one click of the submit button. The funny thing is, it works with TWO clicks of the submit button, I think because by then all the async AJAX calls are finally done. But after the first click, I have an empty array where the movie objects should be.
Here's some code that should summarize what things look like:
var personId
var movies = []
function actorByRating(UserInput) {
Step 1: get actor ID number:
function searchActors() {
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/search/person?query=${UserInput}&api_key=<key>`
}).done(function(response){
personId = response.results[0].id
})
}
Step 2: Use Actor ID to get list of movies, start assigning them attributes:
function getMovies() {
$.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/discover/movie?with_cast=${personId}&vote_count.gte=20&sort_by=vote_average.asc&budget.desc&api_key=<key>&include_image_language=en`
}).done(function(response) {
response.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
getMovieInfo(movie) //step 3
getYouTube(movie) //step 4
saveMovie(movie)
})
})
}
function saveMovie(movie){
movies.push(movie)
}
Step 3 function, takes in a movie object as an argument:
function getMovieInfo(m){
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/movie/${m.movieId}?&api_key=<key>&append_to_results=imdb_id`
}).done(function(response) {
m.revenue = response.revenue
m.budget = response.budget
m.imdbId = response.imdb_id
})
}
Step 4 function, to get Youtube link. Also takes a movie object:
function getYouTube(movie){
$.ajax({
method: "GET",
url: `https://www.googleapis.com/youtube/v3/search?part=snippet&q=${movie.title.split(" ").join("+")}+trailer&key=<key>`
}).done(function(yt){
movie.youtubeLink = `http://www.youtube.com/embed/${yt.items[0].id.videoId}`
})
}
After this, the filtering functions work fine, when they have an array of movies to work with. The problem is, I think, all these successive API calls keep firing before the previous ones are done, and the latter ones need info from the earlier ones to search with. Thus, when I click submit the first time, the final movies array is empty, so the dispatched payload is an empty array. THEN the movie objects get filled in, so when you click submit again, the movies are already there to work with, and the rest of the app works fine.
I've tried everything I can think of to slow the process down, chain promises together (which doesn't work because Step 2 has to run for several movies, i.e. the return values of each function keep changing, so I can't ".then" them), reorganizing the information that comes in...but I can't get it to give me movie objects with all the attributes I need by the time the filtering functions actually run to create the proper payload.
Any help or suggestions would be greatly appreciated!
(Note: the "key" stuff above is just placeholder text)
UPDATE:
I changed the code to basically the following:
searchActors()
.then(function(response){
const actorId = response.data.results[0].id
return actorId
})
.then((personID) => {
return getMoviesFromPersonID(personID)
})
.then(function(response) {
const movieList = []
response.data.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
movieList.push(movie)
// saveMovie(movie)
})
return Promise.all(movieList)
})
.then((movieList) => {
const deepMovieList = []
movieList.forEach((movie) => {
getMovieInfo(movie)
.then(function(response) {
movie.revenue = response.data.revenue
movie.budget = response.data.budget
movie.imdbId = response.data.imdb_id
deepMovieList.push(movie)
})
})
return Promise.all(deepMovieList)
})
.then((deepMovieList) => {
const finalMovies = []
deepMovieList.forEach((movie) => {
finalMovies.push(getYouTube(movie))
})
return Promise.all(finalMovies)
})
Everything works fine right up until the first mention of "deepMovieList". I can't seem to figure out how to have that particular step to work properly, as it essentially involves making 20 API calls with each movie in the movieList. I can't figure out how to 1) get the info back from the API, 2) assign the attributes to the movie object that is passed in to getMovieInfo, and then 3) push that movie object (with the new attributes) to an array that I can use Promise.all on, all without interrupting the promise chain.
Either it moves on to the next "then" function too early (while deepMovieList is still an empty array), or, with other random stuff I've tried, the array ends up being undefined.
How can I have the next "then" function wait until 20 API calls have been made and each movie object has its updated attributes? This will also run into the same problem in the next step, for the YouTube link.
Thanks!
TL;DR: use fetch and promises instead of jQuery, group promises using Promise.all.
The Longer Version
Ok, I'm not going to repeat all your code here. I'm going to abbreviate some stuff to keep it simple.
Basically, you have a bunch of tasks to perform. I'm going to pretend each of them is a function that returns a promise which is resolved with the data you want.
searchActor() - returns a promise resolved with some ID number
getMoviesFromActorID(actorId) - returns a promise that resolves with an array of movie IDs
getMovie(movieId) - returns a promise that resolves with the details for the given movie ID
getYoutube(movie) - returns a promise that resolves with the Youtube embed code.
Given this basic setup (and I admit I'm leaving out a lot of stuff), the code looks like this:
// search for an actor
searchActor('Brad Pitt')
// then get the movie IDs for that actor
.then((actorId) => getMoviesFromActorID(actorId))
// then iterate over the list of movie IDs & build an array of
// promises. Use Promise.all to create a new Promise which is
// resolved when all are resolved
.then((movieIdList) => {
const promiseList = [];
movieIdList.forEach((id) => promiseList.push(getMovie(id)));
return Promise.all(promiseList);
})
// then get Youtube links for each of the movies
.then((movieDetailsList) => {
const youtubeList = [];
movieDetailsList.forEach((movie) => youtubeList.push(getYoutube(movie)));
return Promise.all(youtubeList);
})
// then do something with all the information you've collected
.then((finalResults) => {
// do something interesting...
});
The key to this is Promise.all (documentation here), which will take an array of Promises (or any other iterable object containing promises) and create a new Promise which will resolve when all of the original promises have resolved. By using Promise.all, you can create a step in your promise chain which can include a variable number of parallel actions which must complete before the next step.
You could do something like this will jQuery and callbacks, but it would be pretty darn ugly. One of the great benefits of promises is the ability to lay out a series of steps like above.

Categories

Resources