Javascript how to chain multiple promises - javascript

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.

Related

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

When I use mockImplementationOnce, the new function doesn't get the parent function's args

I have these 2 following methods, which I created a test using jest to the one called removerFromDb
initializeData = async (collectionName) => {
getSomeDataFromDatabase()
this.#data = data;
}
removerFromDb = async (collectionName, _id) => {
await database(async (db) => {
const collection = db.collection(collectionName);
await collection.deleteOne({ _id: ObjectId(_id) });
});
this.initializeData(collectionName);
}
my test looks like that:
const data = require('../data/testingData.json');
it('Should remove item from list', async () => {
let dataCopy= data;
database
.mockImplementationOnce(() => {
dataCopy= dataCopy.filter((item) => item._id != _id);
})
.mockImplementationOnce(() => dataCopy);
await DataManager.removerFromDb("", 1); // the second param serves as _id
expect(DataManager.getData().length).toEqual(5);
});
Now, if you take a look at removerFromDb, you can see that it accept a second parameter named _id. But in my test when I call the method with 1 the mockImplementationOnce function I created doesn't get the value, instead it gets undefined. I found a way to make it work by doing this:
database
.mockImplementationOnce(() => {
const _id = 1; // notice I added this line
replicatedData = replicatedData.filter((item) => item._id != _id);
})
But it's less ideal and I don't understand why I'm not getting the value I sent through the parent function removerFromDb.
Is there any way to do it? Why isn't it working the first way?

Chaining unknown number promises in a recursive function

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

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

Categories

Resources