How to receive data from a nested promise - javascript

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 })
})
}

Related

Is there a way to make an api call within a map of another api call?

I know the title is quite confusing, I wasn't sure how to word it better. What I am trying to do is to fetch some items, map through those items to display them, but the problem is that one of those items has a value of what needs to be another api call to access it.
This is what I'm trying to do:
First of all I am storing an empty state, which later on becomes the data of the fetched items:
const [data, setData] = useState([]);
I'm using axios to fetch and store the data:
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then((response) => {
console.log(response.data.results);
const newData = response.data.results.map((item) => ({
name: item.name,
homeworld: () => {
axios.get(item.homeworld).then((response) => {
response.data.results;
});
},
}));
setData(newData);
})
.catch((error) => {
console.log("error", error);
});
};
It works with the name because it's a simple value. However, the homeworld includes a link that needs to be called once again in order to access it, instead of being a simple value like the name in this case. How can I call it and access what values are held within that link, and display them instead of just displaying the url?
I hope this can help you:
const [data,setData] = useState([])
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then(response => {
console.log(response.data.results);
const { results } = response.data;
for (const item of results) {
axios.get(item.homeworld).then(({data}) => {
setData([...data,{ name: item.name, homeworld: data.results }]);
});
}
})
.catch(error => {
console.log("error", error);
});
};
or with fetch:
const [data,setData] = useState([])
fetch("https://swapi.dev/api/people").then(re=>re.json())
.then(response => {
const newData = []
const { results } = response;
const newData = [];
for (const item of results) {
fetch(item.homeworld).then(re => re.json()).then((data) => {
newData.push({ name: item.name, homeworld: data });
});
}
console.log(newData)
setData(newData)
})
.catch(error => {
console.log("error", error);
});
Use Promise.all()
You can use Promise.all() method to get all the information you need by creating an array of promises by mapping the response.results array with an async function.
This is the code example
const fetchItems = async () => {
const req = await axios.get("https://swapi.dev/api/people");
const response = await req.data;
const allDataPromises = response.results.map(async (item) => {
const itemReq = await axios.get(item.homeworld);
const itemResponse = await itemReq.data;
return {
name: item.name,
homeworld: itemResponse,
};
});
const allData = await Promise.all(allDataPromises);
};
For further information about Promise.all()

Promise inside a loop inside an async function

I am working on a project using react and firebase and redux and I have some items that did created by a user. I'm storing the id of the user in the item object so i can populate the user later when i get the item to display.
Now I'm trying to get the items and modify them by replacing the user id with the actual info about the user but I have a promises problem. In my code I just get an empty array which mean the modification didn't get resolved before I return the final result.
export const getItems = () => {
return (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const items = [];
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
return dbRef
.get()
.then((res) => {
const firstVisible = res.docs[0];
const lastVisible = res.docs[res.docs.length - 1];
async function getData(res) {
/////////////////////////////////////////////// how to finish this code befor jumping to the return line
await res.forEach((doc) => {
firestore
.collection('users')
.doc(doc.data().owner)
.get()
.then((res) => {
items.push({ ...doc.data(), owner: res.data() });
});
});
////////////////////////////////////////////////
return { docs: items, lastVisible, firstVisible };
}
return getData(res);
})
.catch((err) => {
console.log(err);
});
};
};
I don't get exactly what you are trying to do, but I would suggest putting some order to make your code easy to read and work with.
You can use for of to manage async looping. I suggest something like this, disclaimer, I did it at the eye, problably there are some errors, but you can get the idea.
const getAllDocs = function (data) {
let temp = [];
data.forEach(function (doc) {
temp.push(doc.data());
});
return { data: temp };
};
const getDoc = snap => (snap.exists ? { data: snap.data() } : {});
export const getItems = () => {
return async (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
const usersRef = firestore.collection('users');
let temps = [];
const { data: items } = await dbRef.get().then(getAllDocs);
const firstVisible = items[0];
const lastVisible = items[items.length - 1];
for (const item of items) {
const { data: user } = await usersRef.doc(item.owner).get().then(getDoc);
const owner = {
/* whatever this means*/
};
temps.push({ ...user, owner });
}
return { docs: temps, lastVisible, firstVisible };
};
};
The problem is that an array of Promises is not itself a Promise -- so awaiting it will be a no-op.
You can solve this using Promise.all if you want to load them all asynchronously.
const items = await Promise.all(res.map(async (doc) => {
const res = await firestore.collection('users').doc(doc.data().owner).get();
return { ...doc.data(), owner: res.data() };
});
Otherwise you can await in a for loop as suggested in other answers.

Promise fetching data

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);
});

How to loop thorugh mongoDB with a promise and a forEach loop? I want to populate an Array

I am trying to loop through elements in my MongoDB database and save the values to an Array.
However, after hours of struggling I can't get it done.
Here's my code:
//shopController.js
const Product = require('../models/product');
const User = require('../models/user');
exports.getCart = (req, res, next) => {
const productIds = [];
const productsArr = [];
let userData = null;
User.findById(req.user._id)
.then(user => {
userData = user;
userData.cart.items.map(prodId => {
productIds.push(prodId.productId);
})
console.log(productIds); // [ 5dc1b6ace13a97588620d6c6, 5dc1b6ace13a97588620d6c6 ]
return productIds;
})
.then(prodIds => {
prodIds.forEach(prodId => {
Product.findById(prodId)
.then(product => {
productsArr.push(product);
})
.catch(err => console.log(err))
})
console.log(productsArr); // []
})
.catch((err) => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
}
Don't mind the first output, it's intended to display the same ID twice.
The result when I am logging the productsArr[] is always an empty Array, except i place the console.log(productsArr); inside the forEach() loop, which i don't want because it gets logged too often and I can't render an EJS page like this.
The render function would look like this:
res.render('shop/cart', {
path: '/cart',
docTitle: 'Cart',
products: productsArr,
userData: userData
});
I can't get the products into the productsArr[], as soon as i try to access the productsArr[] outside of the forEach() loop I got an empty Array, so I don't know how to go about this.
Do you have any advice?
Thanks in advance!
You need to render it after the Promises all resolve using Promise.prototype.all, there's no other way to get the populated productsArr array.
Try this:
//shopController.js
const Product = require('../models/product');
const User = require('../models/user');
exports.getCart = (req, res, next) => {
const productIds = [];
// const productsArr = []; not needed anymore
let userData = null;
User.findById(req.user._id)
.then(user => {
userData = user;
userData.cart.items.map(prodId => {
productIds.push(prodId.productId);
})
console.log(productIds); // [ 5dc1b6ace13a97588620d6c6, 5dc1b6ace13a97588620d6c6 ]
return productIds;
})
.then(prodIds => {
Promise.all(prodIds.map(prodId =>
Product.findById(prodId)
).then(productsArr => {
res.render('shop/cart', {
path: '/cart',
docTitle: 'Cart',
products: productArr,
userData: userData
});
console.log(productsArr); // [ ... products ... ]
}).catch(err => {
console.log(err);
})
})
.catch((err) => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
}
You are not waiting for your product promises to be finished, to display your data. You can use the Promise.all([]) function to wait for multiple promises at once.
.then(prodIds => {
Promise.all(prodIds.map(prodId => {
return Product.findById(prodId)
.then(product => {
productsArr.push(product);
})
.catch(err => console.log(err))
})).then(() => {
console.log(
})
Here we are mapping every product ids to a promise (Product.findById) and then passing all of them into the Promise.all() function, which takes an array.
We then wait for all the promise to be resolve, and call the then function.
This way, you are sure all of your promise are finished before printing your data.

empty array outside nested resolve promise

im tring to push the return value of the resolve to a variable catWithItems which is outside the resolve. inside the resolve the catWithItems works as expected but when i console log catWithItems outside the loop it returns an empty array.
function categoriesSearch(req, res, next) {
let categories = req.batch_categories;
let catWithItems = [];
_.forEach(categories, (category) => {
return new Promise(resolve => {
pos.categoriesSearch(req.tenant, category.id)
.then(item => {
if(item) category.items = item[0];
return category;
})
.then(category => {
catWithItems.push(category);
console.log(catWithItems); //this is works inside here
return resolve(catWithItems);
});
});
});
console.log(catWithItems); //doesn't work returns empty array
res.json({categoryWithItems: catWithItems });
}
this is the pos.categoriesSearch module. it makes a api call to square.(this works as expected)
function categoriesSearch(tenant, category) {
let search_items_url = ${tenant.square.api.v2}/catalog/search,
apiKey = tenant.square.api.key,
payload = {
"object_types": ["ITEM"],
"query": {
"prefix_query": {
"attribute_name": "category_id",
"attribute_prefix": category
}
},
"search_max_page_limit": 1
},
conf = config(search_items_url, apiKey, payload);
return request.postAsync(conf)
.then(items => {
return items.body.objects;
});
}
Your not handling promises right. Try it this way.
function categoriesSearch(req, res, next) {
let categories = req.batch_categories;
let promiseArray = []; // create an array to throw your promises in
let catWithItems = [];
categories.map((category) => {
let promise = new Promise(resolve => {
pos.categoriesSearch(req.tenant, category.id)
.then(item => {
if(item) category.items = item[0];
return category;
})
.then(category => {
catWithItems.push(category);
console.log(catWithItems); //this is works inside here
return resolve(catWithItems);
});
});
promiseArray.push(promise) // add promises to array
});
// resolve all promises in parallel
Promise.all(promiseArray).then((resolved) => {
console.log(resolved);
res.json({categoryWithItems: catWithItems });
})
}
It should be much easier. Not sure if it works, but something to start with:
function categoriesSearch(req, res) {
const categoryWithItems$ = req.batch_categories.map(category =>
pos.categoriesSearch(req.tenant, category.id)
.then(item => ({ ...category, items: item[0] })
);
Promise.all(categoryWithItems$)
.then(categoryWithItems => res.json({ categoryWithItems });
}

Categories

Resources