Promise fetching data - javascript

I want to get certain data from this site https://swapi.co/.
I use Promises to get data about planets, then films within a certain planet object. Afterwards, I need to get data in the species array within the films array. Everything works up to this point.
My code for fetching info:
const task = planetId => {
const url = `https://swapi.co/api/planets/${planetId}/`;
const getPlanet = () => { // getting the planet by its Id
return new Promise(function(resolve, reject) {
https
.get(`${url}`, function(res) {
res.on("data", function(d) {
const planetData = JSON.parse(d.toString());
resolve(planetData);
});
})
.on("error", function(e) {
reject(e);
console.error(e);
});
});
};
getPlanet().then(gotPlanet => {
const planet = gotPlanet;
const filmsArray = planet.films;
const filmsArrayUrl = filmsArray.map(it => {
return new Promise(function(resolve, reject) { // getting films array
https
.get(it, function(res) {
res.on("data", function(d) {
const films = JSON.parse(d.toString());
resolve(films);
});
})
.on("error", function(e) {
reject(e);
console.error(e);
});
});
});
Promise.all(filmsArrayUrl).then(gotFilms => {
const filmsNew = gotFilms;
planet.films = filmsNew;
const speciesArray = planet.films.map(it => it.species);
const speciesArrayUrl = speciesArray.map(it => it.map(el => { // trying to get the species data
return new Promise(function(resolve, reject) {
https.get(el, function(res) {
res.on('data', function(d) {
const speciesFetched = JSON.parse(d.toString())
resolve(speciesFetched)
})
}).on('error', function(e) {
reject(e)
console.error(e)
})
})
}))
Promise.all(speciesArrayUrl).then(species => {console.log(species)})
});
});
};
The last line results in the console as [Array[5], Array[20], Array[9]] with each element inside the array as Promise {<pending>}.
What should I change in the code to get all species object and return the final result - a planet with fetched data on films and species within the films?

Your code is very hard to follow, i suggest breaking out the reusable parts into their own promise such as the getDataObject piece below. You can then reuse that promise anytime you need to make an HTTP request.
const getDataObject = url => fetch(url).then(res => res.json());
const task = planetId => {
const planetUrl = `https://swapi.co/api/planets/${planetId}/`;
let planet
return getDataObject(planetUrl)
.then(planetResponse => {
//get the planet response
planet = planetResponse
//map through each film in the planet and get its film
let filmsArrayUrls = planet.films.map(filmUrl => getDataObject(filmUrl));
return Promise.all(filmsArrayUrls)
})
.then(allFilms => {
//update the planet with the response for each film
planet.films = allFilms;
//map through all the species in the films
let speciesArray = planet.films.map(film => film.species);
//map through the species elements using Promise.All
let speciesArrayUrl = speciesArray.map(species => Promise.all(species.map(el => getDataObject(el))))
//Promise.All is itself a Promise, so you still need to resolve the Array of Promise.All objects inside of the speciesArrayUrl
return Promise.all(speciesArrayUrl)
})
.then(species => {
//return the species and do something with them
for (let i = 0; i < species.length; i ++) {
planet.films[i].species = species[i]
}
console.log(planet)
return planet
})
};
task(2)

It's because speciesArrayUrl contains an array of an array of promises. You need to flatten out the array first.
One of the things you can do to flatten it out is to apply a reducer on speciesArray by appending .reduce((items, item) => items.concat(item), []).
I rewrote your example using fetch so that it could run in the browser:
const task = planetId => {
const url = `https://swapi.co/api/planets/${planetId}/`;
const getPlanet = () => { // getting the planet by its Id
return fetch(url).then(res => res.json());
};
getPlanet().then(gotPlanet => {
const planet = gotPlanet;
const filmsArray = planet.films;
const filmsArrayUrl = filmsArray.map(it => {
return fetch(it).then(res => res.json());
});
Promise.all(filmsArrayUrl).then(gotFilms => {
const filmsNew = gotFilms;
planet.films = filmsNew;
const speciesArray = planet.films.map(it => it.species)
.reduce((items, item) => items.concat(item), []);
const speciesArrayUrl = speciesArray.map(it => { // trying to get the species data
return fetch(it).then(res => res.json());
})
Promise.all(speciesArrayUrl).then(species => {console.log(species)})
});
});
};
task(2);
I also took a stab at refactoring the code to make it more readable:
function getPlanet(planetId) {
return fetch(`https://swapi.co/api/planets/${planetId}/`)
.then(res => res.json());
}
function getFilms(planet) {
return Promise.all(planet.films.map(f => fetch(f).then(res => res.json())));
}
function getSpecies(film) {
return Promise.all(film.species.map(s => fetch(s).then(res => res.json())));
}
getPlanet(2)
.then(planet => getFilms(planet))
.then(films => Promise.all(films.map(film => getSpecies(film))))
.then(filmSpecies => [].concat(...filmSpecies)) // flatten array of films and film species
.then(species => {
console.log(species);
});

Related

Assigned returned value from function chain into a variable in javascript

Hello I'm having a little problem when I try to assign returned value from function into a variable. I've tried this code with a console.log and it displays the right result but when I want to assign this result to a variable it gives undefined value. So here is the code and can u explain it to me what am I doing wrong because I'm a javascript noobie.
const onDataChange = (items) => {
let products = [];
let images = listFromStorage();
//a function call that displays undefined value but should return array
console.log(images);
items.forEach((item) => {
let key = item.key;
let data = item.val();
products.push({
key : key,
title : data.title,
before : data.before,
after : data.after
})
})
setProductList(...productList, products);
}
const listFromStorage = () => {
let storageRef = firebaseService.getStorage().ref().child('/posts/products');
let images = [];
storageRef.listAll().then(function (res) {
res.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
images.push({
url : url
});
});
});
return images;
})
.catch(function (error) {
console.log(error);
});
}
You need to not only wait for the asynchronous code to finish, but you need to also return a value from listFromStorage to assign.
const onDataChange = async (items) => { // <-- declare async
const products = [];
const images = await listFromStorage(); // <-- await result
console.log(images);
items.forEach((item) => {
const key = item.key;
const data = item.val();
products.push({
key: key,
title: data.title,
before: data.before,
after: data.after
})
})
setProductList(...productList, products);
}
const listFromStorage = () => {
const storageRef = firebaseService
.getStorage()
.ref()
.child('/posts/products');
const images = [];
return storageRef // <-- return Promise chain
.listAll()
.then(function (res) {
res.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
images.push({ url });
});
});
return images;
})
.catch(function (error) {
console.log(error);
});
}

How to make two calls to two REST APIs correctly with javascript without using async await?

I need to make two calls to two rest api, in the first with one of the data obtained, pass it to the second url and ai obtain what I want, I could achieve it with the code below but my boss tells me that it is wrong, first it I did with asyn await and it told me not to use it, then later with fetch and axios but it is not well written, what would be the correct way to do it with both axios and fetch cases?
with axios
axios.all([axios.get(`${urlRestApi}`)]).then(
axios.spread(response1 => {
let arrPost = response1.data.map(res => {
return {
titulo: res.title.rendered,
contenido: res.content.rendered,
extracto: res.excerpt.rendered,
idImagen: res.featured_media,
};
});
console.log("AQUI", arrPost);
arrImg = arrPost.map(image => {
axios.get(`${urlImage}/${image.idImagen}`)
.then(urls => { // urls returns 10 objects each with a corresponding image
arrUrl.push(urls.data); //for this reason I put all 10 in an array, but it happens 10 times
if (arrUrl.length === 10) { // that's why when .length is 10 I go through it and assign what I want
let arrImage = arrUrl.map(img => {
return {
imagenes: img.source_url,
};
});
console.log("TEST", arrImage);
mostrarHTML(arrImage, arrPost); //I already have everything I run my function to print the data obtained
}
});
});
})
);
and with fetch
fetch(`${urlRestApi}`)
.then(respuesta => {
return respuesta.json();
})
.then(data => {
let arrPost = data.map(data => {
return {
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
};
});
console.log(arrPost);
arrImg = arrPost.map(image => {
fetch(`${urlImage}/${image.idImagen}`)
.then(res => {
return res.json();
})
.then(urls => { // // urls returns 10 objects each with a corresponding image
arrUrl.push(urls); //for this reason I put all 10 in an array, but it happens 10 times
if (arrUrl.length === 10) { // that's why when .length is 10 I go through it and assign what I want
arrImage = arrUrl.map(image => {
return {
imagenes: image.source_url,
};
});
console.log("aqui", arrImage);
mostrarHTML(arrImage, arrPost); //I already have everything I run my function to print the data obtained
}
});
});
})
.catch(error => {
console.log(error);
});
With fetch, without async/await:
fetch(urlRestApi)
.then((respuesta) => respuesta.json())
.then((data) => {
const posts = data.map((data) => ({
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
}));
// Create an array of promises that fetch image data and combines it
// with the original post.
const imagePostPromises = posts.map((post) => {
return fetch(`${urlImage}/${image.idImagen}`)
.then((res) => res.json())
.then((imageData) => ({
// Combine the original post with the image data fetched
...post,
imageData,
}));
});
// Return a promise that resolves only when all of the `imagePostPromises` have finished.
return Promise.all(imagePostPromises);
})
.then((postsWithImages) => {
console.log(postsWithImages);
});
And, much more readably, if only you could use async/await,
async function doThings() {
const respuesta = await fetch(urlRestApi);
const data = await respuesta.json();
const posts = data.map((data) => ({
titulo: data.title.rendered,
contenido: data.content.rendered,
extracto: data.excerpt.rendered,
idImagen: data.featured_media,
}));
const imagePostPromises = posts.map(async (post) => {
const res = await fetch(`${urlImage}/${image.idImagen}`);
const imageData = await res.json();
// Combine the original post with the image data fetched
return ({
...post,
imageData,
});
});
const postsWithImages = await Promise.all(imagePostPromises);
console.log(postsWithImages);
}

Can't iterate over my API generated array using the forEach() method

After troubleshooting with console.log/debugger it seems like I cannot iterate over my API generated array at the forEach method call in the function addListItem.
However I can see the pokemonNameList array being populated in the forEach iteration in the loadList function.
What am I doing wrong?
const apiUrl = 'https://pokeapi.co/api/v2/pokemon/?limit=15';
const pokemonNameList = [];
function getAll() {
return pokemonNameList;
}
function add(pokemon) {
if (typeof pokemon === 'object') {
pokemonNameList.push(pokemon);
}
}
function loadList() {
return fetch(apiUrl)
.then((response) => response.json())
.then((data) => {
data.results.forEach((item) => {
fetch(item.url)
.then((response) => response.json())
.then((inneritem) => {
const pokemon = {
name: inneritem.name,
height: inneritem.height,
weight: inneritem.weight
};
add(pokemon);
console.log(pokemonNameList);// I can see the array here
});
});
})
.then(() => {
console.log(pokemonNameList);
})
.catch((e) => {
console.error(e);
});
}
function addListItem(pokemon) {
console.log('I cannot see this console log');//This does not show up
const card = document.createElement('li');
const cardbody = document.createElement('div');
const name = document.createElement('h1');
card.classList.add('card');
cardbody.classList.add('card-body');
name.classList.add('card-title');
name.innerText = pokemon.name;
cardbody.appendChild(name);
card.appendChild(cardbody);
pokemonList.appendChild(card);
}
loadList()
.then(() => {
getAll().forEach((item) => {
console.log('Hello from inside the forEach');//I cannot see this
addListItem(item);
});
})
.catch((e) => {
console.error(e);
});
The problem is that you are not waiting for the inner fetch(item.url)s so when you call getAll no item has been pushed yet.
you can do that by changing forEach to map, returning the promise and adding a promise.all... something like this:
function loadList() {
return fetch(apiUrl)
.then((response) => response.json())
.then((data) => {
return Promise.all(data.results.map((item) => {
return fetch(item.url)
...
I created all the functions up to the place where you mentioned the error
const pokemonNameList = []; // Pokemon Array
const apiUrl = 'https://pokeapi.co/api/v2/pokemon/?limit=15'; // API URL
// To prevent duplicates, in case of calling the loadList function multiple times, i'm passing the index from the response, to replace the element at the same index
const add = (pokemon, index) => pokemonNameList[index] = (pokemon);
const getAll = _ => pokemonNameList; // Short arrow function to return pokemonNameList
async function loadList() {
const response = await fetch('https://pokeapi.co/api/v2/pokemon/?limit=5');
const result_1 = await response.json();
Promise.all(result_1.results.map((item, index) => fetch(item.url).then(response_1 => response_1.json()).then(({
name,
height,
weight
}) => add({
name,
height,
weight
}, index)))).then(() => getAll().forEach(pokemon => console.log(pokemon)));
}

How can I use a for loop to reiterate a promise function?

I have the following code that is used to get JSON data from an Amazon Web Server API.
var json1 = new Promise((resolve, reject) => {
fetch(url[0])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
I have this repeating 14 times using different urls and json vars and have it return the promises at the end using.
return Promise.all([json1,json2,json3,json4,json5,json6,json7,json8,json9,json10,json11,json12,json13,json14]).then(function(values) {
return values;
});
This works, but it takes up 150+ lines. I want to make a for loop that runs through the same code using a for loop. I created this...
for(var jsonCount = 0;jsonCount<url.length-1;jsonCount++){
jsonArr[jsonCount] = new Promise((resolve, reject) => {
fetch(url[jsonCount])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
}
This doesn't work because the promise functions come back as undefined even though it is called by an await function.
const data = await fetchURL(urlToQuery())
Does anyone have suggestions to make this work? There is JSON being returned.
Thanks for your help.
Edit: Here is a larger chunk of the code.
function fetchURL(urls) {
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
return arr;
});
(async function() {
const data = await fetchURL(urlToQuery())
console.log(data);
for(var r=0;r<numStations;r++){
if (data[r] == ""){
onlineArr[r] = false;
wdDataArr[r].push(cardinalToDeg(stationHistAvgArr[r]));
wsDataArr[r].push(0);
You can use .map for the loop. But don't use new Promise. You don't need a new promise when fetch already provides you with one.
Also, call your array urls instead of url. A plural will be a good indication for the reader of your code that indeed it is a collection of URLs.
Here is how it could look:
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
// process your data
for (let obj of arr) {
console.log(obj);
}
});
I think this example can helps you:
// Mock async function
const getDataAsync = callback => {
setTimeout(
() => callback(Math.ceil(Math.random() * 100)),
Math.random() * 1000 + 2000
)
}
// Create the promise
const getDataWithPromise = () => {
return new Promise((resolve, reject) => {
try {
getDataAsync(resolve);
} catch(e) {
reject(e);
}
});
}
// Using the promise one time
getDataWithPromise()
.then(data => console.log("Simple promise:",data))
.catch(error => console.error(`Error catched ${error}`));
// Promises compound: Promise.all
const promise1 = getDataWithPromise();
promise1.then(data => console.log("promise1 ends:",data));
const promise2 = getDataWithPromise();
promise2.then(data => console.log("promise2 ends:",data));
const promise3 = getDataWithPromise();
promise3.then(data => console.log("promise3 ends:",data));
const promise4 = getDataWithPromise();
promise4.then(data => console.log("promise4 ends:",data));
const promise5 = getDataWithPromise();
promise5.then(data => console.log("promise5 ends:",data));
Promise.all([promise1,promise2,promise3,promise4,promise5])
.then(data => console.log("Promise all ends !!",data));
Hope this helps
you will have issues with closure and var variable capture.
You may want to change var to let to capture the right value in the closure so that url[jsonCount] is actually what you want.
also I think it would be much easier to do something like that in one line :)
let results = [];
for(let i = 0; i < urls.length; ++i) results.push(await (await fetch[urls[i]]).json());
This is a good use for map, mapping urls to promises...
function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
return Promise.all(promises).then(results => {
return results.map(result => result.json())
})
}}
// url is your array of urls (which would be better named as a plural)
fetchUrls(url).then(results => {
// results will be the fetched json
})
Using the async/await syntax (equivalent meaning)
// this can be called with await from within another async function
async function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
let results = await Promise.all(promises)
return results.map(result => result.json())
}

How to receive data from a nested promise

I have 2 models defined in Sequelize that are related using a one to many relationship and then used the Sequelize instance to fill the database.
Connection = new Sequelize({...});
Const Recipe = Connection.define('recipe', {name: Sequelize.STRING})
Const Ingredient = Connection.define('ingredient', {name: Sequelize.STRING})
Recipe.hasMany(ingredients);
Ingredient.belongsTo(Recipe);
_.times(3, () => {
return ProteinRecipe.create({
name: `SOMENAME`})
.then((recipe) => {
_.times(3, () => {
return recipe.createIngredient({
name: `INGREDIENT FROM :${recipe.name}`
})
What I would like to do is retrieve all the ingredient data from all of the recipes.
I have tried
const readWithPreferences = (req, res) => {
Recipe.findAll()
.then((recipes) => {
return Promise.all(recipes.map((recipe) => {
let recipeObj = {};
recipeObj.info = recipe.dataValues;
recipeObj.ingredients = [];
recipe.getIngredients()
.then((ingredients)=>{
return Promise.all(ingredients.map((ingredient)=>{
recipeObj.instructions.push(ingredient.dataValues);
}));
});
return recipeObj;
}))
.then((recipesArray) => {
let responseObj = {};
responseObj.data = recipesArray;
res.status(200).send(responseObj);
})
});
}
When I check to see if the data is being accessed in the inner promise call, the logger is showing the data. But I am only receiving the information from the outer promise array. How can I return the data from the inner promise array?
You are not returning the inner promise in the outer Promise.all callback.
const readWithPreferences = (req, res) => {
Recipe.findAll().then(recipes => {
return Promise.all(recipes.map(recipe => {
let recipeObj = { info: recipe.dataValues }
return recipe.getIngredients()
.then(ingredients => {
recipeObj.instructions = ingredients.map(i => i.dataValues)
// now, return the whole recipe obj:
return recipeObj
})
}))
})
.then(data => {
res.status(200).send({ data })
})
}

Categories

Resources