Chaining unknown number promises in a recursive function - javascript

I'm trying to find the best way to go about this service call where I retain all the data in a single object. The call returns an object that has a property of next_page_url. If there is a next_page_url the function should keep chaining. Since I don't know what the url is until the next call resolves I need to call these in order and resolve them in order. I'm also collecting data from each call. I haven't been able to figure out what the structure should be
what I have so far
getDataFromAllPages = (url) => {
waniKaniAxios.get(url).then(object => {
if(object.data.pages.next_url){
return this.getDataFromAllPages(object.data.pages.next_url.replace(waniKaniAxios.defaults.baseURL, ''));
}
});
}
getWanikaniData = () => {
this.getDataFromAllPages('/subjects?types=vocabulary').then(result => {
console.log(result);
});
}

Abstract away the wanikaniaxios.get in another function to make recursion clearer.
Here's my badly formatted code (don't know how SF editor works) , feel to ask any questions if you have any. Happy coding.
getWanikaniData = () => {
this.getDataFromAllPages("/subjects?types=vocabulary")
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err); // always put a .catch when you're using prmomises.
});
};
getDataFromAllPages = async (url) => {
// using async await;
try {
let arr = []; // i am assuming you'll improve upon what data structure you might want to return. Linked list seems best to me.
const object = await waniKaniAxios.get(url);
if (object.data.pages.next_url) {
const laterData = await this.getDataFromAllPages(
object.data.pages.next_url.replace(waniKaniAxios.defaults.baseURL, "")
);
arr = [...arr, ...laterData];
} else {
arr = [...arr, object];
}
Promise.resolve(arr);
} catch (err) {
Promise.reject(new Error(`Oops new wanikani error, ${err}`));
}
};
FINAL UPDATE
Using part of the answer below I managed to get it working. Had to partially give up on the recursion aspect because I didn't how to make the promise resolve into data
Here's the final solution that I came up with
getDataFromAllPages = async (url) => {
let results = {};
try {
//getting intial data
const initialData = await waniKaniAxios.get(url);
//using the intial data and mapping out the levels then saving it into results object
results = this.mapOutLevels(initialData.data, results);
//get the next page url
let nextPageUrl = initialData.data.pages.next_url;
//while there is a next page url keep calling the service and adding it to the results object
while (nextPageUrl) {
const laterData = await waniKaniAxios.get(nextPageUrl);
nextPageUrl = laterData.data.pages.next_url;
results = this.mapOutLevels(laterData.data, results);
}
} catch (err) {
Promise.reject(new Error(`Opps new wanikani error, ${err}`));
}
return Promise.resolve(results);
};
getWanikaniData = () => {
this.getDataFromAllPages("/subjects?types=vocabulary")
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
});
};

Related

Is there a way to use async/await with this algolia example code?

This code i am about to put here does not work in the respect that my graphQL server always receives an empty array back... i know this is because the call completes before the data is available.. i don't have much experience with promises so i am wondering how i can get it to work, i played with a bunch of different combinations to attempt to await the response so that i could just pass back an array of id's to the playground... the browsewr.on(end) part works fine and the proper data shows in the console? Anyone care to shed some light for me?
const browser = await lookbookIndex.browseAll();
let hits = [];
let returnArray = [];
browser.on('result', (content) => {
hits = hits.concat(content.hits);
});
browser.on('end', () => {
console.log('Finished!');
console.log('We got %d hits', hits.length);
returnArray = hits ? hits.map(a => a.objectID) : [];
});
browser.on('error', err => err);
return returnArray;
Async syntax automatically wraps everything to Promise instance.
But you also can return Promise instance manually. It accepts callback with resolve & reject arguments. And you can call resolve to resolve the promise:
async function search() {
const browser = await lookbookIndex.browseAll();
return new Promise((resolve, reject) => {
let hits = [];
browser.on('result', (content) => {
hits = hits.concat(content.hits);
});
browser.on('end', () => {
console.log('Finished!');
console.log('We got %d hits', hits.length);
resolve(hits ? hits.map(a => a.objectID) : [])
});
browser.on('error', err => reject(err));
})
}
And you can await the promise:
await new Promise(...)
Similar questions:
Return data from function

Need help in fetching document with reference id in firestore using reactjs

I'm a newbie with firestore and reactjs. I'm trying to write a simple react code to fetch documents from 2 collections
user-habits {email, habitid}
habits {habitid, content +few more fields}
each user can have one or more habits
I'm trying to execute the following react code to
export async function getHabit(habitid) {
let result = [];
return db.collection('habits')
.where('habitid','==',`${habitid}`)
.get()
.then(snapshot => {
snapshot.forEach(doc => {
result.push({...doc.data()})
})
return result;
})
}
export async function getUserHabit(email) {
let result = [];
return db.collection('user-habits')
.where('email','==',`${email}`)
.get()
.then(snapshot => {
snapshot.forEach(doc => {
let habitid = doc.data().habitid
let habit = getHabit(habitid)
result.push({...doc.data(), ...habit, uid:doc.id})
})
return result;
})
}
For some reason, I always get blank data on getHabit call. console log statement, shows that a Promise is returned by getHabit, which resolves after the code is completely executed. Thanks in advance
You can use async and await inside the getUserHabit() function to wait for the promise returned by getHabit() to be resolved
export async function getUserHabit(email) {
let result = [];
return db.collection('user-habits')
.where('email','==',`${email}`)
.get()
.then(snapshot => {
snapshot.forEach(async doc => {
let habitid = doc.data().habitid
let habit = await getHabit(habitid)
console.log(habit) // will log the habit value
result.push({...doc.data(), ...habit, uid:doc.id})
})
return result;
})
}
You don't need to return the db.collection in this case. Since you're calling from the front end, you can just reference the collection path and wait for Firestore to return what you're looking for. Also, you don't need to use the .then() approach with async/await. If you're expecting an array in return, try something like this:
export async function getHabit(habitid) {
let result = [];
let get = await db.collection('habits').where('habitid','==',`${habitid}`).get()
get.forEach(doc => {
result.push({...doc.data()})
})
return result;
})
}
export async function getUserHabit(email) {
let result = [];
let get = await db.collection('user-habits').where('email','==',`${email}`).get()
get.forEach(doc => {
let habitid = doc.data().habitid
let habit = getHabit(habitid)
result.push({...doc.data(), ...habit, uid:doc.id})
})
return result;
})
}

React - Returning data from API

I know there are similar questions, but I can't find the answer.
First, please tell me if I'm doing something really wrong
I need to populate my state with data from an API call. This was working fine with code above:
export const GetPlanets = async () => {
const planets = await axios.get(`${BASE_URL}`).catch((e) => {
console.error(e);
})
return planets.data.results
}
But then I needed to make a second call to several links from one json response filed, and I managed to make it work (don't know if it is the correct approach, though)
const GetPlanets = async () => {
let planetas = {}
await axios.get(`${PLANETS_URL}`)
.then((p) => {
planetas = p.data.results
return axios.get(`${FILMS_URL}`)
}).then((f) => {
planetas.films.forEach((v, i) => {
planetas[i].film = f
})
})
})
.catch((e) => {
console.error(e);
})
return planetas
}
This is my component file, where I try to get the object, like I was doing before
useEffect(() => {
const fetchPlanetas = async () => { // ME TRYING...
const planetas = await GetPlanets()
setPlanetas(planetas)
setToShow(planetas[0])
};
fetchPlanetas()
}, [])
But all I get is undefined
You're getting an array of undefined because .map() needs a return value. In both your .map() callbacks, you are not returning anything.
const results = [1, 2, 3, 4]
const results2 = results.map(elem => {
elem = elem + 1
})
console.log(results2)
But, even if you did return something in your .map() callback, GetFilms(f) is asynchronous, so you would not get the results of GetFilms() mapped into the array as you would expect.
You have a couple of options:
If you have access to the API, send the films data along with the rest of the data when you do your first request.
Use async/await and Promise.all() to get responses.

Recursive Promise.all with a snapshot in firebase

I have the following structure on my firebase database:
I need to get the values of the keys pin. For that I'm working with a recursive function like this:
let pins = [];
const normalize = (snapchot) => {
snapchot.forEach(function(child) {
if(child.val().pin) {
pins.push(Promise.resolve(child.val().pin));
}
else normalize(child);
});
return Promise.all(pins);
}
And now, call the normalize function:
normalize(snapshot) // snapshot represents the data from the firebase db
.then(p => {
console.log(p); // [ 'mi-pin-agosto', 'mi-pin-julio' ]
})
.catch(err => {
// handle error
})
And it works, but when I debug that code, I see that return Promise.all(pins); gets called more than one time. I only need to be called only once, after the foreach have been completly finished; that's with the idea for the case of performance, because the snapshot data it's more large than the see it in the image I show.
Any ideas ???
to only use Promise.all once you can have the recursive function as a function "inside" `normalize
const normalize = (snapshot) => {
const process = x => {
let ret = [];
x.forEach(function(child) {
if(child.val().pin) {
ret.push(Promise.resolve(child.val().pin));
} else {
ret = ret.concat(process(child));
}
});
return ret;
});
return Promise.all(process(snapshot));
}
This code also doesn't require a global array to store the results
However, as there is nothing asynchronous about any of the code you are calling - dispense with the Promises inside normalize
const normalize = (snapshot) => {
let ret = [];
snapshot.forEach(function(child) {
if(child.val().pin) {
ret.push(child.val().pin);
} else {
ret = ret.concat(normalize(child));
}
});
return ret;
};
If you really have to use Promises for this code, you can simply
Promise.all(normalize(snapshot))
.then(p => {
console.log(p); // [ 'mi-pin-agosto', 'mi-pin-julio' ]
})
.catch(err => {
// handle error
})

Javascript how to chain multiple promises

In parse I have this crazy query where I'm querying on table1 to get an object.
Then querying table2 on a pointer column that matches table1's result for all users that match that.
Next I need to create an object and then create another object using the result of that first object.
Finally save the final object into the users from table2's query.
I'm having an issue chaining everything and for some reason my success message returns before the user objects are saved.
Parse.Cloud.define('startChain', (req, res) => {
let q1 = new Parse.Query("Table1");
q1.equalTo("objectId", req.params.q1ID);
q1.equalTo("user", req.user);
q1.include("user");
q1.get(req.params.q1ID)
.then(post => {
post.get("user")
.then(user => {
// Query on q1
let q2 = new Parse.Query("Table2");
q2.equalTo("t1Object", post);
w2.include("user2");
q2.include("pointer2Object");
q2.find();
})
.then(users => {
var morePromises = users.map(aUser => {
let newObject = new Parse.Object.Extend("Table3");
newObject.set("user", aUser);
newObject.set("table1Pointer", post);
newObject.save()
.then(result => {
var object2 = new Parse.Object.Extend("Table4");
object2.set("column1", aUser);
object2.set("column2", result);
var object3 = new Parse.Object.Extend("Table5");
object2.save()
.then(o2 => {
object3.set('column', 'o2');
object3.save()
.then(o3 => {
aUser.set("o3", o3);
return aUser.save(null, {useMasterKey: true});
});
});
});
});
Promise.all(morePromises)
.then(result => res.success());
})
.catch(err => {
res.error(err.message);
});
});
});
In the first lines
q1.get(req.params.q1ID)
.then(post => {
the argument to then()'s callback is whatever is returned by q1.get().
Following the same logic, you can chain promises on a single level (i.e. not nested) by returning from a chain block what you need in the next. E.g the above could continue like
q1.get(req.params.q1ID)
.then(post => {
...
return q2.find();
}).then( users => {
// users is available here
...
return object2.save();
}).then( o2 => {
});
And so forth...
Ideally you should use async await: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
would be much cleaner, code example below:
async function yourFunction(x) {
const result = await q2.find();
result.map((newObject) => {
await newObject.save();
});
}
not sure your browser support tho.

Categories

Resources