Assigning axios response whilst iterating through array of objects - javascript

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

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

Promise Map keeps returning null

Been on this for over 24 hours, everything seems to work as I want but the promise keeps returning null.
[
null,
null
]
here are my codes:
let vettedBatch = currentBatch.Items.map((current) => {
getUser(current.userId).then((res) => {
// return resolve(JSON.parse(res.body));
let body = JSON.parse(res.body);
if (body.hasOwnProperty("Item")) {
return body;
} else {
//if user does not exist on the users table based on ID, lets take his transaction out of the fail-safe table
console.log(
`user with id number ${current.userId} with transaction id ${current.txId} do not exist or must have been disabled`
);
User.deleteWithdrawalTx(current.txId).then(() => {
console.log(
`transaction id ${current.txId} delete for unknown user with userId ${current.userId}`
);
});
}
});
});
You need to use Promise.all:
const data = [];
const promises = currentBatch.Items.map(async current => {
return await getUser(current.userId)
});
Promise.all(promises)
.then(res => {
res.map(item => {
let { body } = JSON.parse(item);
if (body.hasOwnProperty("Item")) {
data.push(body);
} else {
console.log('message');
}
})
})
Instead of mixing async/await and Promise syntax, I would suggest you to stick to one.
Here would be your code written fully in async/await syntax:
const getData = async () => { // async function instead of new Promise()...
return Promise.all(currentBatch.Items.map(async (current) => { // make map async and await it with Promise.all()
const res = await getUser(current.userId); // await instead of .then()
let body = JSON.parse(res.body);
if (body.hasOwnProperty("Item")) {
return body;
} else {
console.log(`user with id number ${current.userId} with transaction id ${current.txId} do not exist or must have been disabled`);
await User.deleteWithdrawalTx(current.txId); // await instead of .then()
console.log(`transaction id ${current.txId} delete for unknown user with userId ${current.userId}`);
// You should return something here too, but I dont know what you want to return, so...
}
}));
}
let vettedBatch = await getData(); // await the async function
Your problem is actually a deviation of this question: How do I return the response from an asynchronous call?. Should be fixed in my answer, but I still suggest you to check out the linked thread.

Trying to figure out how to go around Promise: Pending situation

I could not find a proper way to get my GET handler working. In the code below, when I send a GET request to that endpoint, my array element are all showing as 'Promise: Pending'. Can you suggest a way to go around it? I tried using setTimeout() method but I feel like it is not a proper solution.
Thank you in advance.
apiRouter.get('/photos', (req,res,next) => {
axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
.then(response => {
const photosArr = response.data.data;
const arr = photosArr.map(id => {
return axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
.then(response => {
return response.data.media_url;
})
})
res.send(arr);
next();
})
})
This would be a useful case to use async / await
The problem is that you are returning the promise in your Array.map(). Even though you have a .then block after your promise, the promise itself is what is being returned because this is running asynchronously.
Something like this should be close
apiRouter.get('/photos', async (req,res,next) => {
const response = await axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
const photosArr = response.data.data;
const arr = photosArr.map(async (id) => {
const resp await axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
return resp.data.media_url;
};
const final = await Promise.all(arr);
res.send(final);
next();
})
You can use Promise.all() to wait for all promises in an array to resolve:
apiRouter.get('/photos', (req,res,next) => {
axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
.then(response => {
const photosArr = response.data.data;
Promise.all(photosArr.map(id => {
return axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
.then(response => {
return response.data.media_url;
})
})).then(photos => {
res.send(photos);
next();
})
})
})```

Javascript promise chain hell

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

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