So i need to use the result of a promise to make another fectch request, i'm working with the rest api for wordpress, and i need the id of subcategories inside the post object to retrieve that category name and build two arrays one with the posts and another with categories names availables.
Here is my function
function fetchAccordionData()
{
const id = document.querySelector('.acc').getAttribute('data-id'),
wpRestAPI = '/wp-json/wp/v2/';
return fetch(wpRestAPI + 'posts?per_page=100&categories=' + id)
.then((resp) => resp.json())
.then((data) =>
{
let curId = [], subCatList = [];
data.map((post) =>
{
let catList = post.categories.filter((c) => c !== parseInt(id));
fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
});
});
return {'subCatList':subCatList, 'posts':data}
});
}
Now when i call the function the subCatListarray isn`t ready yet:
fetchAccordionData().then((data) =>
{
console.log(data.subCatList, data.posts);
for(let cat of data.subCatList)
{
console.log(cat);
}
});
So, how do i know when the promise of the second fetch is resolved so i can use the data?
You'll need to place all of your promises in an array and use Promise.all to wait for all of those promises to resolve before accessing subCatList.
Your modified code would look like so:
function fetchAccordionData() {
const id = document.querySelector('.acc').getAttribute('data-id'),
wpRestAPI = '/wp-json/wp/v2/';
return fetch(wpRestAPI + 'posts?per_page=100&categories=' + id)
.then((resp) => resp.json())
.then((data) => {
let curId = [], subCatList = [];
// promises is an array of promises
let promises = data.map((post) => {
let catList = post.categories.filter((c) => c !== parseInt(id));
// return a promise on each iteration
return fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
});
});
return Promise.all(promises)
.then(() => ({'subCatList':subCatList, 'posts':data}));
});
}
Notice that the last step returns the object {'subCatList': subCatList, 'post': data} only after every promise in promises has resolved. That way, you can be confident that the promises in the array are finished making their push into subCatList.
It's also worth noting that the interface of fetchAccordionData stayed exactly the same, so you should be able to use it as you did in your original example:
fetchAccordionData().then((data) => {
console.log(data.subCatList, data.posts);
for(let cat of data.subCatList) {
console.log(cat);
}
});
It looks like
return {'subCatList':subCatList, 'posts':data}
is outside of the part of your function that gets categories from the rest API:
data.map((post) =>
{
let catList = post.categories.filter((c) => c !== parseInt(id));
fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
});
});
so your function is returning it before it can fetch the category data from the API. If you put your return statement after the last then statement it should return the data your are looking for:
data.map((post) =>
{
let catList = post.categories.filter((c) => c !== parseInt(id));
fetch(wpRestAPI + 'categories/' + catList[0])
.then((r) => r.json())
.then((cat) =>
{
if(!curId.includes(cat.id)) subCatList.push({id: cat.id, name: cat.name});
curId.push(cat.id);
return {'subCatList':subCatList, 'posts':data}
});
});
Related
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);
}
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)));
}
I am trying to replace the loop that send out http requests using Axios. http is an Axios object and returns a promise. I want to change the code so that I use Promises.all() instead of a loop. I am trying to create a Promise, push into an array and then pass on to Promises.all. I only get empty arrays in my promises array.
I would appreciate any pointers on what I am doing wrong.
// Converting this
responseData = [];
for (const record of response.records) {
let response = await http.get('/records/' + record.id);
responseData.push(response.data.data);
}
// I am trying to convert to this ..
let promises = [];
for (const record of response.data.data) {
let promise = new Promise((resolve, reject) => {
let response = http.get('/records/' + record.id)
.then(response => {
return response.json();
})
.then(resp => {
// console.log(resp.data.data);
//return resp.data.data
resolve(resp.data.data);
});
return response;
});
promises.push(promise);
}
Promise.all(promises).then(records);
Not entirely sure of whether response.json() is required, and where the .records and .data.data should go ... but this might work:
const promises = [];
for (const record of response.records) {
promises.push(
http.get('/records/' + record.id)
.then(response => response.json()) //maybe?
.then(response => response.data.data)
)
}
Promise.all(promises).then(records => { /* do something */ } );
or use map:
const promises = response.records.map(record =>
http.get('/records/' + record.id)
.then(response => response.json()) //maybe?
.then(response => response.data.data)
)
Promise.all(promises).then(responseData => {/* do something */} );
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())
}
I Have a array of objects which i need to clone with different values.
Those values i'll get from each promise finally after preparing main modified array of object i'll have to save this. So i many need this as one single promise.
I not sure how to do it.
Here is the example we need to clone oldUser data. Say old user has credit score = 100; but for new user default credit will be created randomly by system.
For each user in the array of users few details has to get updated using async call.
This is the requirement
function getUserCreditScore(user){
var url = '/someurl';
return $http.get(url).then(function(res){
user.creditScore = (res.data) ? res.data : 0;
});
}
function getUserRecomandations(user){
var url = '/someurl';
return $http.get(url).then(function(res){
user.recommendation = (res.data) ? res.data : 'basic recommendation';
});
}
function getUserHelpInfo(user){
var url = '/someurl';
return $http.get(url).then(function(res){
user.helpInfo = (res.data) ? res.data : 'Help Info';
});
}
function clone(){
var newUsers = angular.copy(oldUsers);
for (var i=0; i<newUsers.length; i++){
newUsers[i].id = undefined;
getUserCreditScore(newUsers[i]);
getUserRecommendation(newUsers[i]);
getUserHelpInfo(newUsers[i]);
}
var promises = _.map(newUsers, user => user.save());
$q.all(promises).then(function (data) {
console.log(data);
}
}
You'll need to Promise.all on an array of Promises that are returned by getScreditScore
something like
function getCreditScore(){
var url = '/someurl';
return $http.get(url).then(res => (res && res.data) ? res.data : res);
}
function clone(){
var newUsers = angular.copy(oldUsers);
Promise.all(
newUsers.map(newUser => {
newUser.id = undefined;
return getCreditScore()
.then(result => newUser.creditScore = result);
})
).then(results => // results will be an array of values returned by the get in getCreditScore(newUser)
Promise.all(newUsers.map(user => user.save()))
).then(data =>
console.log(data); // this will be the result of all the user.save
);
}
Note: the newUser.creditScore is set in the .then in the newUsers.map callback - (minimal change to my original answer)
Alternatively, passing user to getCreditScore
function getCreditScore(user){
var url = '/someurl';
return $http.get(url)
.then(res => (res && res.data) ? res.data : res)
.then(score => user.creditScore = score);
}
function clone(){
var newUsers = angular.copy(oldUsers);
Promise.all(
newUsers.map(newUser => {
newUser.id = undefined;
return getCreditScore(newUser);
})
).then(results => // results will be an array of values returned by the get in getCreditScore(newUser)
Promise.all(newUsers.map(user => user.save()))
).then(data =>
console.log(data); // this will be the result of all the user.save
);
}
Personally, I'd write the code
function getCreditScore(){
var url = '/someurl';
return $http.get(url).then(res => (res && res.data) ? res.data : res);
}
function clone(){
var newUsers = angular.copy(oldUsers);
Promise.all(
newUsers.map(newUser => {
newUser.id = undefined;
return getCreditScore()
.then(result => newUser.creditScore = result)
.then(() => newUser.save())
.then(() => newUser);
})
).then(data =>
console.log(data); // this will be the newUsers Array
);
}
This assumes, though, that you don't need to wait for all the $http.get before running the user.save() - in fact this may be a little (very little) more performant as the newUser.save and $http.get will run in tandem
Ok, I know your meaning, you want your every element of your array do something that is async.
So you can use map and Promise.all. Here is my code:
const asyncFunction = (item, cb) => {
setTimeout(() => {
console.log(`done with ${item}`);
cb();
}, 1000);
}
let requests = [1, 2, 3].map((item) => {
return new Promise((resolve) =>{
asyncFunction(item, resolve);
});
});
Promise.all(requests).then(() => console.log('done'));