I am trying to implement a forEach to fetch data from a mongo database and push each data to an array. I need to console log that array after all data is pushed. I tried with a promises but couldn't get desired result. Here's what I've got
count=[];
array.forEach((data)=>{
userDetailsModel.find({UpdateStatusDate:data}).countDocuments()
.exec((err,data)=>{count.push(data)})
})
Thanks for any help!
Make sure your function has async keyword infront of it and you should be good to go
let count = await Promise.all(
array.map(data =>
userDetailsModel
.find({ UpdateStatusDate: data })
.countDocuments()
.exec()
)
);
consle.log(count);
You are missing the point of the promise pattern applied to an array of promises.
Exec will return a promise that will resolve only for one element of the data array.
Thus you will have a promise for each data in your array and your code must wait for all promises to resolve. Keep in mind this will result in one Mongo query per data in your array.
Best way to do so is to map your data array to an array of promises and use Promise.all to wait for all of them to resolve:
// get an array of promises for each data to query:
let promises = array.map(data => {
return userDetailsModel
.find({UpdateStatusDate:data})
.countDocuments()
.exec();
});
// when all the promises fulfill
Promise.all(promises).then(counts => {
console.log(counts);
// for each counts log the result:
counts.forEach(result => { console.log(result); });
});
You could use the Promise.all() method to wait after all promises
let count = [];
const promiseArray = array.map((data) => (
new Promise((resolve) => {
userDetailsModel.find({ UpdateStatusDate: data })
.countDocuments()
.exec((err, data) => { resolve(data) })
})
))
Promise.all(promiseArray).then((values) => {
count = values;
console.log(count);
});
Related
So getAstronautsData make request to API then return array of promises. This promises mast make request to Wiki API and parse response in object. Then exampleAsyncFunc must wait all promises and return one big object with all info about Astronauts.
But if I use Promise.all function ending and console is clear.
function getAstronautsData() {
return new Promise((resolve, reject) => {
getData('http://api.open-notify.org/astros.json', "http", (data) => {
resolve(data) // get Astronauts list from API
})
}).then((astronautsList) => {
return astronautsList.people.map((person => // return array of promises
new Promise(resolve => {
getWikiData(person.name, (data) => { // request on Wiki API
resolve({info: data.extract, img: data.thumbnail.source})
})
})
))
})
}
async function exampleAsyncFunc (){
let promisesList = await getAstronautsData()
// next code just few variant was i try
let data = await Promise.all(promisesList)// it's not working.
console.log(data)
Promise.all(promisesList).then(data => console.log(data)) //it's not working. Function display nothing
promisesList.forEach((promise) => { //it's working but not so elegant
promise.then(data => console.log(data))
})
}
exampleAsyncFunc ()
function getWikiData(searhTerm, callback) {
getData(getUrlString(searhTerm), "https", (data) => {
const regex = new RegExp(searhTerm.replaceAll(" ", ".*"));
for (let page in data.query.pages) {
if (data.query.pages[page].title === searhTerm || regex.test(data.query.pages[page].title)) {
callback(data.query.pages[page])
return
}else{
callback(null)
}
}
})
}
You appear to be using Promise.all correctly, but if any of the Promises in Promise.all rejects, then overall Promise.all promise will reject too and nothing will happen, where in your forEach version it'll simply skip those promises silently and move on to the next entries.
Likewise if any of the promises in the list stays pending: if so then the Promise.all promise will never resolve. This could be because you have a long list of return values and the whole list takes a longer-than-expected time to resolve, or because your getWikiData call encounters an error and you don't pass that out to reject that particular promise in your array.
You can debug this behavior by ensuring that each of your calls to then is followed by .catch(console.error) (or some more robust error handler).
Let me first disclose that I am a big promise partisan and frankly deplore callbacks. The implication here is that I would not have written your getData and getWikiData with callbacks.
I will also point out that I second what #t.niese said in the comments: Because it does not make sense having both let data = await Promise.all(promisesList) and promisesList.forEach((promise) => {.
Anyway, your code is unnecessarily complex and can be simplified like so:
function getAstronautsData(callback) {
getData('http://api.open-notify.org/astros.json', "http", data => {
callback(data.people.map(person =>
new Promise(resolve => {
getWikiData(person.name, data => {
resolve(data);
})
}))
)
})
}
function exampleAsyncFunc (){
getAstronautsData(promises => {
Promise.all(promises)
.then(result => {
//result will contain those resolved promises
console.log(result);
})
});
}
exampleAsyncFunc ()
Notice that I am passing a callback to getAstronautsData and call it from inside that function with the array of promises you ultimately want to resolve. No need for async here either as you can see.
Ok, problem was in API (in API one of astronauts have name "Tom Marshburn" but on Wiki his page have title "Thomas Marshburn") and function getWikiData not return any data on error. So i fixed this problem.
Thanks you all for you help!!!
I have an array with user IDs and I need to find out what name belongs to each ID and return them in an array.
I can get the user's name from the database, using knex and push them into an array, but when I try to send the data it is always an empty array.
I am not really good with Promises so can't figure out how to apply to my project.
const userId = [10,11,12,13]
let users = []
userId.map(id => {
db.select('name').from('users').where('user_id', id)
.then(user => {
users.push(user)
})
})
res.json(users)
I want the response to wait until the looping finishes and send the users array.
Your map is creating an array of undefined because your callback function doesn't return anything. If we tweak it slightly, it'll create an array of promises, which conveniently is exactly what Promise.all expects. :-) So:
const userId = [10,11,12,13]
Promise.all(
userId.map(id => db.select('name').from('users').where('user_id', id))
)
.then(users => { // `users` is an array of users, in the same order as the IDs
res.json(users);
})
.catch(error => {
// Render an error response
});
As an alternative answer, here's how you could make 1 trip to the DB for this particular query meaning you don't need to wait for multiple Promises and reduce the load on your DB
knex.raw(
'select name from users where user_id in (' + userId.map(_ => '?').join(',') + ')',
[...userId]
);
First you need to wait for all promises to finish before running res.json(...)
Second, you shouldn't mutate outside variables after promise resolving (the order by which the promises resolve will alter your output and that is not nice.
Something like this should work fine
const userId = [10,11,12,13]
// map userId array to promise array
// Promise.all aggregates a promise array into one big promise that resolves when all promises resolve (and preserves array order)
Promise.all(
userId.map(id =>
db
.select("name")
.from("users")
.where("user_id", id)
)
)
.then(users => res.json(users))
.catch(e => console.error("Error::", e));
/*handle error in the catch block*/
/* visual example of Promise.all.then block
Promise.all([ users = [
getUser(10), -> {userId: 10, ....}
getUser(11), -> {userId: 11, ....}
getUser(12) -> {userId: 12, ....}
]) ]
*/
You want Promise.all(), see here.
Try
const userId = [10,11,12,13]
let users = userId.map(id => new Promise(resolve => {
db.select('name').from('users').where('user_id', id)
.then(user => {
resolve(user)
})
}))
Promise.all(users).then(()=>res.json(users))
Here users is an array of promises. As soon as all of them are resolved, do res.json(users).
I'm running into the following problem.
I have the following promise (simplified):
module.exports.checkVotes = (groupId) =>{
return new Promise((resolve, reject)=>{
// some db stuff onoing
...
.then((votes)=>{return resolve(votes}})
.catch((err)=>{return reject(err}})
})
}
At some point I'm looping through an object. For each entry i have to call promise above. But before the 2. Promise starts, the first one have to be finished...and so on.
I tried this, but as soon as I call the promise it gets executed.
.then(()=>{
for (let i=0;i<groups.length;i++){
// for each group I want to call the promise later
//but it executes as soon as I push it.
tasklist.push(this.checkVotes(groups[i]))
}
return tasklist.reduce((promiseChain, currentTask) => {
return promiseChain.then(chainResults =>
currentTask.then(currentResult =>
[ ...chainResults, currentResult ]
)
);
}, Promise.resolve([])).then(arrayOfResults => {
console.log(arrayOfResults)
}).catch((err) =>{console.log(err)});
})
})
I can not run this with Promise.all() because for some database things, I need this to run sequentially. Moreover I can not hardcode this because the number of groups is variable.
How can I solve this?
Thanks for helping
Your problem was with putting promises in the taskList, not tasks (functions that return promises). The idea behind making them run in sequence is to call them in the then callback in the reduce:
return groups.reduce((promiseChain, currentGroup) => {
return promiseChain.then(chainResults =>
this.checkVotes(currentGroup).then(currentResult => [...chainResults, currentResult])
// ^^^^^^^^^^^^^^^
);
}, Promise.resolve([]))
But if you can use async/await syntax, you don't need to do all of this anyway and can write the much simpler and more efficient
const results = [];
for (const group of groups)
results.push(await this.checkVotes(group));
seems to work like this:
groupIds = [12, 45, 34];
return groupIds .reduce((promise, groupId) => {
return promise.then(() => {
return this.checkVotes(groupId)
.then((votes)=>{
votesList[groupId] = votes
})
.catch((err)=>{
throw err
})
})
}, Promise.resolve())
I'm currently a little stuck with a race condition, and I'm pulling my hair out. Essentially, I'm querying an API, adding the results to the DB, then doing stuff with the returned/saved data.
I'm less asking about this specific problem, and more the design pattern of how to fix this sort of issue. The line p.push(formatter.createAccounts(account, id)); might run upto 1000 times, and might take 20 seconds or so.
Any ideas on what I'm doing wrong will be super helpful
Thanks,
Ollie
// get all users
Promise.all(updatedUsers)
.then(() => {
// create an array of promises
const p = [];
users.then((user) => {
...
ids.forEach((id) => {
const accounts = foo.getAccounts(id, true); // returns a promise
accounts.then((account) => {
// add the return data from here to the promise array at the bottom
p.push(formatter.createAccounts(account, id));
});
});
});
// this would ideally be a promise array of the data from above - but instead it just evaluates to [] (what it was set to).
Promise.all(p).then(() => {
// do my stuff that relies on the data here
})
});
The problem is that you are not including the foo.getAccounts promise into your promises array. Modified version:
Promise.all(updatedUsers)
.then(() => {
users.then((user) => {
return Promise.all(ids.map(id => {
//for each ID we return a promise
return foo.getAccounts(id, true).then(account => {
//keep returning a promise
return formatter.createAccounts(account, id)
})
}).then((results) => {
//results is an array of the resolved values from formatter.createAccounts
// do my stuff that relies on the data here
})
})
//rest of closing brackets
I need to make several async calls that depend on each other. I initially wrote the code and used Promise.all to make async in steps. I looped through my data and created a async method in order to put all of the needed actions into an array to pass into Promise.all(). This works fine, but how could I do the same using Observables. I've read that forkJoin is the equivalent of Promise.all, but how could i loop through the data and wrap my async function, and then execute it before moving to the next flatMap?
public getMonthly(){
return this.http.get(url)
.flatMap(response => {
// need to convert this?
let actions = response.json().map(this.asyncMonthlyRecord);
return Promise.all(actions);
})
.flatMap(()=> this.queryMonthly())
.map(this.convertFromSQl)
.catch((error:any)=> Observable.throw(error || 'Server Error'));
}
private asyncMonthlyRecord = (record):Promise<any> => {
return this.setUsage(record,'HILowMonthly');
}
private queryMonthly(){
return this.storage.query('SELECT * FROM HILowMonthly')
}
getMonthly().subscribe(x => console.info(x)); // logs data from SQLite perfectly...
I think what you want is something like this
Rx.Observable.of({ itemIds: [1, 2, 3, 4, 5 ]})
.mergeMap(response => {
const promises = response.itemIds
.map(id => {
return new Promise((resolve, reject) => {
// Wait for some random time, then resolve the promise.
const timeout = Math.floor(Math.random() * 5000);
setTimeout(() => {
console.log(`Finished promise ${id}`); // debug only
resolve({id, resolved: true})
}, timeout);
});
});
// Wait until all Promises have been resolved, then return all
// of them in a single and ordered array.
return Rx.Observable.forkJoin(promises);
})
.subscribe(x => console.log(x));
Working code on jsbin
Notice that the promises resolve in an arbitrary order but are returned in the correct order.
The commented code in the jsbin example also shows each promise could be resolved individually and merged back into the original stream in case the order of the promises is not important.