How to get data from the links in an API? - javascript

I need to get some data from this API-page: https://swapi.dev/api/films/2.
Specifically, I need to get the name, gender, and date of birth of each character from the second episode of Star Wars.
Without problems I can get the main data of the film, such as the title, the director, the producer and so on. However, I cannot get the data of the individual characters.
I tried to do this with just one function and thought it would be useful to create a for loop. The loop should fetch each result and then return the searched data to me.
async function GetCharacters() {
const people = fetch("https://swapi.dev/api/films/2/").then(data => {
return data.json();
}).then(films => {
console.log(films.characters)
})
for (let i = 0; i < people.length; i++) {
const request = await fetch(film.characters[i]);
const character = await request.json();
console.log(character.name)
console.log(character.gender)
console.log(character.birth_year)
}
}
GetCharacters()
However, the result I get is different from what I would like:
['https://swapi.dev/api/people/1/', 'https://swapi.dev/api/people/2/', 'https://swapi.dev/api/people/3/', 'https://swapi.dev/api/people/4/', 'https://swapi.dev/api/people/5/', 'https://swapi.dev/api/people/10/', 'https://swapi.dev/api/people/13/', 'https://swapi.dev/api/people/14/', 'https://swapi.dev/api/people/18/', 'https://swapi.dev/api/people/20/', 'https://swapi.dev/api/people/21/', 'https://swapi.dev/api/people/22/', 'https://swapi.dev/api/people/23/', 'https://swapi.dev/api/people/24/', 'https://swapi.dev/api/people/25/', 'https://swapi.dev/api/people/26/']
Instead of getting the searched data for each character, I get an array of links, as if the loop didn't work. Why is that?

There's two issues here. Firstly, the people variable holds a Promise object, not the data returned from the AJAX request. Therefore your for loop is not executing. You need to perform the loop through the characters array within the callback.
Secondly, you're using await in the arrow function which is not async. To fix this you can use the same fetch().then() pattern within the loop to retrieve character information:
function GetCharacters() {
fetch("https://swapi.dev/api/films/2/").then(d => d.json()).then(films => {
films.characters.forEach(url => {
fetch(url).then(d => d.json()).then(character => {
console.log(character.name, character.gender, character.birth_year);
// work with the character data here...
})
})
})
}
GetCharacters()

Related

Axios POST request in cycle

I need to save a list of dishes in my restaurant's menu. I do it like this:
fromFormMenu.forEach(fromFormMeal => {
MealAPI.create(fromFormMeal, restaurant.id).then(resp => console.log(resp))
})
In this case, the dishes are saved to the database in the wrong order in which the user entered them into the form. Here is what my API method looks like:
static async create(meal, restaurantId) {
const response = await axios.post(REST_URL + "/" + restaurantId, meal);
return response
}
It is necessary for me that the order of records was saved such as they were entered by the user. I assume that the problem is related to the asynchrony of requests, because of which the records are stored in random order. I even tried removing the 'async' in the method declaration, but that didn't work.
The reason of getting the wrong order is you're calling all the api at the same time. If some of them is finished faster than the previous items, it will be saved before them.
So in this situation you might wanna call the next api after the first one is finished.
Something like:
const mainFunction = async () => {
for (let i = 0; i < fromFormMenu.length; i++) {
const resp = await MealAPI.create(fromFormMeal, restaurant.id)
console.log(resp)
}
}

Recursion with an API, using Vanilla JS

I'm playing with the Rick and Morty API and I want to get all of the universe's characters
into an array so I don't have to make more API calls to work the rest of my code.
The endpoint https://rickandmortyapi.com/api/character/ returns the results in pages, so
I have to use recursion to get all the data in one API call.
I can get it to spit out results into HTML but I can't seem to get a complete array of JSON objects.
I'm using some ideas from
Axios recursion for paginating an api with a cursor
I translated the concept for my problem, and I have it posted on my Codepen
This is the code:
async function populatePeople(info, universePeople){ // Retrieve the data from the API
let allPeople = []
let check = ''
try {
return await axios.get(info)
.then((res)=>{
// here the current page results is in res.data.results
for (let i=0; i < res.data.results.length; i++){
item.textContent = JSON.stringify(res.data.results[i])
allPeople.push(res.data.results[i])
}
if (res.data.info.next){
check = res.data.info.next
return allPeople.push(populatePeople(res.data.info.next, allPeople))
}
})
} catch (error) {
console.log(`Error: ${error}`)
} finally {
return allPeople
}
}
populatePeople(allCharacters)
.then(data => console.log(`Final data length: ${data.length}`))
Some sharp eyes and brains would be helpful.
It's probably something really simple and I'm just missing it.
The following line has problems:
return allPeople.push(populatePeople(res.data.info.next, allPeople))
Here you push a promise object into allPeople, and as .push() returns a number, you are returning a number, not allPeople.
Using a for loop to push individual items from one array to another is really a verbose way of copying an array. The loop is only needed for the HTML part.
Also, you are mixing .then() with await, which is making things complex. Just use await only. When using await, there is no need for recursion any more. Just replace the if with a loop:
while (info) {
....
info = res.data.info.next;
}
You never assign anything to universePeople. You can drop this parameter.
Instead of the plain for loop, you can use the for...of syntax.
As from res you only use the data property, use a variable for that property only.
So taking all that together, you get this:
async function populatePeople(info) {
let allPeople = [];
try {
while (info) {
let {data} = await axios.get(info);
for (let content of data.results) {
const item = document.createElement('li');
item.textContent = JSON.stringify(content);
denizens.append(item);
}
allPeople.push(...data.results);
info = data.info.next;
}
} catch (error) {
console.log(`Error: ${error}`)
} finally {
section.append(denizens);
return allPeople;
}
}
Here is working example for recursive function
async function getAllCharectersRecursively(URL,results){
try{
const {data} = await axios.get(URL);
// concat current page results
results =results.concat(data.results)
if(data.info.next){
// if there is next page call recursively
return await getAllCharectersRecursively(data.info.next,results)
}
else{
// at last page there is no next page so return collected results
return results
}
}
catch(e){
console.log(e)
}
}
async function main(){
let results = await getAllCharectersRecursively("https://rickandmortyapi.com/api/character/",[])
console.log(results.length)
}
main()
I hesitate to offer another answer because Trincot's analysis and answer is spot-on.
But I think a recursive answer here can be quite elegant. And as the question was tagged with "recursion", it seems worth presenting.
const populatePeople = async (url) => {
const {info: {next}, results} = await axios .get (url)
return [...results, ...(next ? await populatePeople (next) : [])]
}
populatePeople ('https://rickandmortyapi.com/api/character/')
// or wrap in an `async` main, or wait for global async...
.then (people => console .log (people .map (p => p .name)))
.catch (console .warn)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script>/* dummy */ const axios = {get: (url) => fetch (url) .then (r => r .json ())} </script>
This is only concerned with fetching the data. Adding it to your DOM should be a separate step, and it shouldn't be difficult.
Update: Explanation
A comment indicated that this is hard to parse. There are two things that I imagine might be tricky here:
First is the object destructuring in {info: {next}, results} = <...>. This is just a nice way to avoid using intermediate variables to calculate the ones we actually want to use.
The second is the spread syntax in return [...results, ...<more>]. This is a simpler way to build an array than using .concat or .push. (There's a similar feature for objects.)
Here's another version doing the same thing, but with some intermediate variables and an array concatenation instead. It does the same thing:
const populatePeople = async (url) => {
const response = await axios .get (url)
const next = response .info && response .info .next
const results = response .results || []
const subsequents = next ? await populatePeople (next) : []
return results .concat (subsequents)
}
I prefer the original version. But perhaps you would find this one more clear.

Removing nested api call from for loop

i made an api call to return a set of data(ex: users - list type, with the returned data i created a for loop and and within my for loop i make another api call to get the user's profile detail based on the user's id. I know this isn't the best practice and i was wondering how i could go about refactoring it.
api.get(...).then(response => {
this.users = response;
for(let i=0; i<this.users.length; i++){
api.get(...this.users[i].id).then(response => {
if(response.name == this.users[i].name)
this.newList.push(response);
})
}
})
and in my html i loop over this.newList to display the info that i need.
How can i remove the nested api call from within the for loop and still get the same results?
One possible solution is to use async/await. Although this will not remove nested loop, but make code look better
Example
async function getUsersAndProfiles () {
try {
this.users = await api.get(...);
for(let i=0; i<this.users.length; i++){
let response = await api.get(...this.users[i].id);
if (response.name == this.users[i].name)
this.newList.push(response);
}
}
catch (e)
console.log(e);
}
You can even move api call for user profile to another async function for possible future reuse and better code structure
Just push array of request into array after that we can use Promise.all() to make a request at a time, then we could create newList based on results array.
api.get(...).then(response => {
this.users = response;
const promises = this.users.map(user => api.get(...user.id))
const result = Promise.all(promises).then(result => {
this.newList = results.filter((user, i) => user.name === this.users[i].name)
})
})

Axios API call returning Promise object instead of result?

Before I start, let me say that I'm new to Javascript and very new to axios API calls, so I'm probably making a rookie mistake...
I have this function getObjects() that's meant to map over an array and return the data from an Axios API call. The API call and map function are both working, but I'm getting a Promise object instead of the data I'm trying to get.
I figure this is because the data is returned before there's enough time to actually get it, but not sure how to fix? I tried a .setTimeout(), but that didn't seem to work.
getObjects() {
let newsItems = this.state.arrayofids.map((index) => {
let resultOfIndex = axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${index}.json`).then((res) => {
let data = res.data;
//console.log(data.by); // this prints the correct byline, but
// all the bylines are printed after
// the console.log below...
if (!data.hasOwnProperty('text')) return data;
}); /// end of axios request
return resultOfIndex;
}); /// end of map
/// ideally this would end in an array of response objects but instead i'm getting an array of promises...
console.log(newsItems);
}
(The extra escape characters are for my text editor's benefit.)
Here's a link to a codepen with the issue - open up the console to see the problem. It's a React project but I don't think any of the React stuff is the issue. EDIT: Codepen is link to working solution using axios.all as suggested below
Thanks!
EDIT: Here is my working solution.
getObjects() {
let axiosArr = [];
axios.all(this.state.arrayofids.map((id) => {
return axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${id}.json`)
})).then((res) => {
for (let i = 0; i < this.state.arrayofids.length; i++) {
axiosArr.push(<li key={i} data-url={res[i].data.url} onClick={(e) => this.displayTheNews(e)}>{res[i].data.title}</li>);
}
if (axiosArr.length == this.state.arrayofids.length) {
this.setState({arrayofdata: axiosArr});
console.log('state is set!');
}
})
}
axios.all function should be more appropriate to your current scenario.
Your console.log is executing immediately, rather than waiting for the requests to finish, because they are not synchronous. You have to wait for all the responses before you console.log.
OPTION 1 (the hard way):
replace your console.log with
newsItems.forEach((promise, index) => {
promise.then((object)=>{
newsItems[index] = object
if (index+1 == newsItems.length) {
console.log(newsItems)
}
})
})
OPTION 2 (the better way):
using axios.all
getObjects() {
axios.all(this.state.arrayofids.map((id) => {
return axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${id}.json`)
})).then((res) => {
console.log(res)
})
}
by the way, I would definitely reccommend changing
this.state.arrayofids.map((index) => {
let resultOfIndex = axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${index}.json`)...
to be called id instead of index

How to fetch data over multiple pages?

My project is based on React, redux, redux-saga, es6 and I try to fetch data from this API:
http://api.dhsprogram.com/rest/dhs/data/BD,2000,2004,2007?&returnFields=CharacteristicLabel,Indicator,IndicatorId,Value&f=json
As you can see, this specific API call shows data with a limit of 100 data per page spread on 40 pages.
According to this answer:
http://userforum.dhsprogram.com/index.php?t=msg&th=2086&goto=9591&S=Google
it says that you can extend the limit to a maximum of 3000data per page.
However, in some cases I would do an API call that exceeds that limit which means I would not receive all my data doing it like this:
export function fetchMetaData(countryCode: string, surveyYears: string) {
return (fetch('http://api.dhsprogram.com/rest/dhs/data/' + countryCode + ',' + surveyYears + '?returnFields=CharacteristicLabel,Indicator,IndicatorId,Value&f=json')
.then(response => response.json())
.then(json => json.Data.map(survey => survey)))
}
So my question is; what is the best way to get all data from this API given that I know the total pages of data. The answer in the forum link suggest to loop through the API. However, I can't find the right syntax usage to do this.
My idea would be doing one api call to get the total number of pages. Then store this in a state using redux+redux-saga. Then do a new request sending the total pages as parameter and fetch this total number of pages times. And by doing this I can't figure out the syntax to store the data for each iteration.
A possible solution -the idea is to get the number of pages first, then make the appropriate number of API calls, pushing the promise from each call into an array. We then wait for all the promises to resolve, and do something with the returned data.
async function fetchMetaData() {
const response = await fetch('apiUrlToGetPageNumber');
const responses = await Promise.all(
Array.from(
Array(resp.data.pagesRequired),
(_, i) => fetch(`apiUrlToSpecificPage?page=${i}`)
)
);
// do something with processedResponses here
}
Here is another possible solution using async/await. The beauty of this is that the total_pages count is dynamic, so that if it increases while you're processing your request, it'll make sure you get it all.
async function fetchMetaData() {
let allData = [];
let morePagesAvailable = true;
let currentPage = 0;
while(morePagesAvailable) {
currentPage++;
const response = await fetch(`http://api.dhsprogram.com/rest/dhs/data?page=${currentPage}`)
let { data, total_pages } = await response.json();
data.forEach(e => allData.unshift(e));
morePagesAvailable = currentPage < total_pages;
}
return allData;
}

Categories