Javascript promise chain hell - javascript

I'm having a problem with promise chains that I don't know how to resolve. Summary of my code: I do a mongoose query for a specific user, fetch his CarIds and then query each car for his details and return these details via JSON response.
let carsDetails = [];
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
carIds.forEach((carId) => {
Car.findById(carId)
.then(car => {
console.log(car);
carsDetails.push(car);
})
.catch(err => { throw err; });
});
return res.status(200).json({ data: carsDetails });
})
.catch(error => {
throw error;
});
The problem is that no cars get actually pushed onto carsDetails array on the carsDetails.push(car); line, because it jumps to return statement before it manages to fill up the array. Is there a workaround that could do those queries and return a result in a form of an array, object...? I tried writing everything in async await form too with self-made async forEach statement, but it doesn't help me. Any suggestions? I already tried with Promise.all(), but didn't manage to fix the issue. Thanks!

You'll need to collect the promises of your Car.findById(carId) calls and use Promise.all() to wait for all of them before responding. You can use array.map() to map each ID to a promise from Car.findById().
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
const carPromises = carIds.map(carId => Car.findById(carId))
return Promise.all(carPromises)
})
.then(cars => {
res.status(200).json({ data: cars })
})
.catch(error => {
throw error
})

If you can have a lot of cars to find, you may want to do your query in a single request, no need to stack multiple promises :
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
// if carsDetails is not an array of objectIds do this instead :
// const carIds = user.carsDetails.map(id => mongoose.Types.ObjectId(id));
return Car.find({ _id: { $in: carIds });
})
.then(userCars => {
res.status(200).json({ data: userCars })
})

await/async is the way to go, with await/async you use regular for ... of loops instead of forEach.
async function getCarDetails() {
let carsDetails = [];
let user = await User.findById(userId);
const carIds = user.carsDetails;
for (let carID of carIds) {
let car = await Car.findById(carId)
console.log(car);
carsDetails.push(car);
}
return res.status(200).json({
data: carsDetails
});
}
Or you use Promise.all and map instead of for ... of
async function getCarDetails() {
let user = await User.findById(userId);
const carIds = user.carsDetails;
let carsDetails = await Promise.all(carIds.map(carID => Car.findById(carID)));
return res.status(200).json({
data: carsDetails
});
}
Those two solutions are slightly different. The second version with the map will send all requests to the DB at once, and then waits until they are all resolved. The first one will send the request one after another. Which one is better depends on the use-case, the second one could lead to request peeks, and might be easier be abused for DDoS.

Try to use async/await to solve this problem. It will be more readable and clean
async function getCarDetail() {
let carsDetails = []
try {
const user = await User.findById(userId)
const carIds = user.carsDetails
for (let i = 0; i < carIds.length; i++) {
const carId = carIds[i]
const car = await Car.findById(carId)
carsDetails.push(car)
}
return res.status(200).json({ data: carsDetails })
} catch(error) {
console.log(error)
}
}

Related

FindOne inside map returns no results

I'm trying to do a search using FindOne inside map but it never finds the data by Product Id. I don't understand the reason. Im using express on nodejs.
This is my code:
const calc = (details) => {
let grandSubtotal = 0;
details.map( async detail => {
const {verifyProduct} = await Product.find({ _id: detail._id});
console.log(detail._id);
console.log(verifyProduct); // UNDEFINED
...
Should be:
const result = await Promise.all(details.map( async (detail) => { … } ));
when you do it like you done you will get a pending promise object that never going to be resolved, I don’t know if you want to return some results, if no just do await Promise.all
Also this should be:
const calc = async (details) => { … }
Mongoose find returns a list of results. findOne returns an object.
The code is doing the equivalent of:
const {verifyProduct} = []
Use findOne to get an object to destructure, and test the result before use.
details.map( async (detail) => {
const res = await Product.findOne({ _id: detail._id });
if (!res) {
//throw new Error('No id '.detail._id)
console.log('No id', detail._id)
}
const { verifyProduct } = res
console.log(detail._id);
console.log(verifyProduct);
}
Also (as #antokhio noted), if you want to use the returned result array of the details.map you will need to await those as well.
You don't need await here
Product.find(({ _id }) => _id === detail._id );

Assigning axios response whilst iterating through array of objects

I'm looking to assign a value to an array of objects inside a Axios call. I can't figure out how to assign the values to the new array. Below is the code I have so far.
Thank you.
app.get('/matchedusers', (req, res) => {
plexClient.getAllUsers().then(plexUsers => {
axios.get('https://tautulli.link/api/v2?apikey=API-KEY&cmd=get_users_table&length=150')
.then((tautulliUser) => {
let tautulliUsers = tautulliUser.data.response.data.data
let matchedUsers = []
let timedUsers = []
// Doing a check to see if tautulli user is also a plex user.
tautulliUsers.forEach((tautulliUser) => {
plexUsers.forEach((plexUser) => {
if(tautulliUser.username == plexUser.username || tautulliUser.username == plexUser.email){
matchedUsers.push({id: plexUser.id, username: plexUser.username, email: plexUser.email})
}
})
})
matchedUsers.forEach((user) => {
axios.get(`https://tautulli.link/api/v2?apikey=API-KEY&cmd=get_user_watch_time_stats&user_id=${user.user_id}&query_days=7`)
.then((watchTime) => {
// I want to add the matched user to timedUsers and add the watchTime response. Something like "timedUsers.push({...user, response: response})"
// This is the part that I can't figure out.
})
})
// Use the timedUsers array to display the results.
console.log(timedUsers)
res.json(timedUsers)
})
})
})
Use Promise.all(arrayOfPromises).then(...) to wait for an array of promises to finish.
If you ever introduce async code in a Javascript function, you always need to wait for the async response to finish, so you can only access the array inside the next .then().
Changing the entire function to use the async/await syntax can make this a little nicer to work with.
Promise.all(matchedUsers.map((user) =>
axios.get(`https://tautulli.link/api/v2?apikey=API-KEY&cmd=get_user_watch_time_stats&user_id=${user.user_id}&query_days=7`)
.then((response) => ({ ...user, response }))
).then(res => {
timedUsers = res;
}
Note the matchedUsers.map((user) => axios.get... syntax, which doesn't put a curly brace around the function body. This is an implicit Javascript return, so the map will return an array of the axios get promises.
Same for (response) => ({ ...user, response }), which is an explicit return of an object from a function. You must wrap the object in ( ) to signify it's an implicit object return.
You should use Promise.all, also I converted your route to async/await syntax as it is much easier to read and work with. It is not recommended to await in the loop it is better to push all the promises to an array and then use Promise.all to wait for the all.
app.get('/matchedusers', async(req, res) => {
const allUsers = await plexClient.getAllUsers();
const tautulliUser = await axios.get('https://tautulli.link/api/v2?apikey=API-KEY&cmd=get_users_table&length=150');
let tautulliUsers = tautulliUser.data.response.data.data
let matchedUsers = []
let timedUsers = []
tautulliUsers.forEach((tautulliUser) => {
plexUsers.forEach((plexUser) => {
if (tautulliUser.username == plexUser.username || tautulliUser.username == plexUser.email) {
matchedUsers.push({
id: plexUser.id,
username: plexUser.username,
email: plexUser.email
})
}
})
})
matchedUsers.forEach((user) => {
const response = axios.get(`https://tautulli.link/api/v2?apikey=API-KEY&cmd=get_user_watch_time_stats&user_id=${user.user_id}&query_days=7`);
timedUsers.push({ ...user,
response
});
})
const final = await Promise.all(timedUsers);
console.log(final)
res.json(final)
})
I suggest you use async/await syntax, and use Promise.all and map to resolve the request promises first then add them to timedUsers array.
app.get("/matchedusers", async (req, res) => {
const plexUsers = await plexClient.getAllUsers();
const tautulliUser = await axios.get(
"https://tautulli.link/api/v2?apikey=API-KEY&cmd=get_users_table&length=150"
);
let tautulliUsers = tautulliUser.data.response.data.data;
let matchedUsers = [];
let timedUsers = [];
// Doing a check to see if tautulli user is also a plex user.
tautulliUsers.forEach((tautulliUser) => {
plexUsers.forEach((plexUser) => {
if (
tautulliUser.username == plexUser.username ||
tautulliUser.username == plexUser.email
) {
matchedUsers.push({
id: plexUser.id,
username: plexUser.username,
email: plexUser.email,
});
}
});
});
// I want to add the matched user to timedUsers and add the watchTime response. Something like "timedUsers.push({...user, response: response})"
// This is the part that I can't figure out.
timedUsers = await Promise.all(
matchedUsers.map(async (user) => {
const response = await axios.get(
`https://tautulli.link/api/v2?apikey=API-KEY&cmd=get_user_watch_time_stats&user_id=${user.user_id}&query_days=7`
);
return { ...user, response }
})
);
// Use the timedUsers array to display the results.
console.log(timedUsers);
res.json(timedUsers);
});

Firestore slow queries are causing the entire logic to crash

I am currently designing a todo app with react and firebase without any node js server code. Everything was fine when inserting data and authenticating, but when I tried to query all the tasks where uid equals a certain value(that means all the tasks that belongs to a particular user), somehow the query gets skipped and returns null. But then, after a second or so, the array got retrieved and printed out.
The code:
function retrieveTasksOfUser(uid) {
// get all tasks where the uid equals the given value
return db.collection("tasks").where("uid", "==", uid).get();
}
function retrieveTasks() {
let res = [];
retrieveTasksOfUser(currentUser["uid"])
.then((snapshot) => {
snapshot.forEach((doc) => {
// include the doc id in each returned object
let buffer = doc.data();
buffer["id"] = doc.id;
res.push(buffer);
});
console.log(res);
return res;
})
.catch((err) => {
console.error("Error retrieving tasks: ", err);
return res;
});
}
let tasks = retrieveTasks();
console.log({ tasks });
EDIT:
Inspired by Frank's answer, I modified my code and got:
async function retrieveTasks() {
let res = [];
const snapshot = await retrieveTasksOfUser(currentUser["uid"]);
snapshot.forEach((doc) => {
let buffer = doc.data();
buffer["id"] = doc.id;
res.push(buffer);
});
return res;
}
let tasks = [];
let promise = retrieveTasks();
promise.then((res) => {
console.log("Tasks are");
tasks = res;
console.log(res);
});
console.log(tasks);
But the result turns out to be an empty array
Thanks a bunch in advance.
You're not returning anything from the top-level code in retrieveTasks, so that means that tasks will always be undefined.
The simplest fix is to return the result of the query, which then means that your return res will be bubbled up:
return retrieveTasksOfUser(currentUser["uid"])
...
But this means you're returning a promise, as the data is loaded asynchronously. So in the calling code, you have to wait for that promise to resolve with then:
retrieveTasks().then((tasks) => {
console.log({ tasks });
})
You can make all of this a lot more readable (although it'll function exactly the same) by using the async and await keywords.
With those, the code would become:
async function retrieveTasks() {
let res = [];
const snapshot = await retrieveTasksOfUser(currentUser["uid"]);
snapshot.forEach((doc) => {
let buffer = doc.data();
buffer["id"] = doc.id;
res.push(buffer);
});
return res;
}
let tasks = await retrieveTasks();
console.log({ tasks });
Or with a bit more modern JavaScript magic, we can make it even shorter:
async function retrieveTasks() {
const snapshot = await retrieveTasksOfUser(currentUser["uid"]);
return snapshot.docs.map((doc) => { ...doc.data, id: doc.id });
}
let tasks = await retrieveTasks();
console.log({ tasks });

How to fetch data on every element in an array using array.map method

I want to fetch data for every object in an array and return an array of new objects with the previous and newly fetched data.I got stucked on getting my result array as my function is returning an array of resolved undefined promises.
I am using a flight search api thats using the apca function for fetching
export const searchApcaLocation = async (dataArr,setDeals) => {
const promises = await dataArr.map(async item => {
apca.request(item.destination);
apca.onSuccess = (data) => {
return fetch('http://localhost:3050/googlePlaceSearch',{
method:"post",
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
cityName:data.airports[0].city
})
})
.then(res => res.json())
.then(imagelinkData => {
const locationObject = {
data: item,
imagelink: imagelinkData.link
}
return locationObject
})
.catch(err => console.log('error on image search',err))
};
apca.onError = (data) => {
console.log('error',data)
};
})
const results = await Promise.all(promises)
return results
}
can someone guide me please on what am I doing wrong?
edit:
as I am trying to fix it realized the problem is I am not returning anything in my map function but if trying to return the apca.onSuccess I am getting an array of functions
just return is missing before fetch function. since you're not returning your promise result it's giving undefined.
export const searchApcaLocation = async (dataArr,setDeals) => {
const promises = await dataArr.map(async item => {
apca.request(item.destination);
apca.onSuccess = (data) => {
return fetch('http://localhost:3050/googlePlaceSearch',{
method:"post",
headers:{'Content-Type':'application/json'},
body:JSON.stringify({
cityName:data.airports[0].city
})
})
.then(res => res.json())
.then(imagelinkData => {
const locationObject = {
data: item,
imagelink: imagelinkData.link
}
return locationObject
})
.catch(err => console.log('error on image search',err))
};
apca.onError = (data) => {
console.log('error',data)
};
})
const results = await Promise.all(promises)
return results
}
The issue in your case might be, that you are using async/await and then blocks together.
Let me sum up what is happening :
1) you await dataArray.map
2) within the map callback, you use the onSuccess method of apca
3) within this method you are using then blocks which won't await until you got a response.
At this point where you return the locationObject, your function already reached the return statement and tries to return results.
But results are of course undefined because they never get resolved at all.
Also, keep in mind that your function returns another promise because you used async/await which you have to resolve where you imported it.
Cheers :)

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