Cannot figure out how to wait for Promise - javascript

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

Related

Javascript JSON Fetch with await returns data but its function return an empty array

I'm trying to fiddle with fetching data from public APIs and then showing that data in a React component, the react part is not important tho, this is primarly a js issue. I'm using PokeApi for learning purpose, I create a new object with the data I need from the request, and then push that object to an array that the function returns when called:
// fetchPoke() function
let pokemons = []
// IDs is just an array with 20 random numbers between a range
IDs.forEach(async (id) => {
let url = `https://pokeapi.co/api/v2/pokemon/${id}`
await fetch(url)
.then((res) => {
if (!res.ok) {
console.log(`${id} not found`)
} else {
return res.json()
}
})
.then((data) => {
let pokemon = {}
pokemon.id = data.id
pokemon.name = data.name
pokemon.image = data.sprites.other.dream_world.front_default
pokemons.push(pokemon)
})
})
// function returns the array of object
return pokemons
But whenever I call this function
let pokemons = fetchPoke()
And then log it
console.log(pokemons)
Although I can see the content, it says it's an array of 0:
In fact if I try to console log pokemons.length I get 0
What could cause this? Am I doing something wrong in my fetch request?
So, you create an empty array.
You loop through you loop through the array, firing-off a bunch of asynchronous requests that will, as a side-effect, populate the empty array when the promises complete.
You immediately return the array without waiting for the promises to complete.
The requests probably have not even left your machine by the time this happens.
The array is empty at this point.
If instead, you declare your function as async and you map your IDs to a new array of promises, then await them all with Promise.all, then the promises will be able to complete before the function returns, and the resolved value of Promise.all will be an array containing your pokemons.
async function getSomePokemons(IDs) { // note, this is an async function
const promises = IDs.map((id) =>
fetch(`https://pokeapi.co/api/v2/pokemon/${id}`)
.then((res) => {
if (!res.ok) {
console.log(`${id} not found`)
// implicitly resolves as undefined
} else {
return res.json()
}
})
.then((data) => (data ? { // data might be undefined
id: data.id,
name: data.name,
image: data.sprites.other.dream_world.front_default
} : undefined))
)
const pokemons = await Promise.all(promises);
return pokemons;
}

Wait until forEach fetch data from db

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

get data from array of objects containg promises

In my react app I have some user ids and need to fetch their emails and names from different endpoints so what i do is:
const promises = ids.map(
id => ( {email: axios.get(`blabla/${id}/email`), name: axios.get(`blabla/${id}/name`)} )
);
and it gets me back:
[
{email: Promise, name: Promise},{email: Promise, name: Promise},{email: Promise, name: Promise},...
]
Now to get the data what i do is:
const results = [];
promises.map(({ email, name}) =>
Promise.all([email, name]).then((result) => {
results.push({
email: result[0].data,
name: result[1].data,
});
})
);
but i have a feeling that it may not be a good way to it, i mean it works now but i don't want to get into any issues later !! haha, for instance, a race between promises, or for instance, a user email set for another user. I don't know if they are even possible to happen but I need to check with you experts that if you confirm this way or do you suggest something else.
It's fine other than that you're not using the array map creates. Any time you're not using map's return value, use a loop (or forEach) instead. Also, any time you're writing to an array you're closing over from asynchronous operation results, it sets you up to accidentally use that array before it's populated
In this case, though, I'd use the array map gives back:
Promise.all(
promises.map(({ email, name}) =>
Promise.all([email, name])
.then(([email, name]) => ({email, name}))
)
).then(results => {
// ...use results here, it's an array of {email, name} objects
})
.catch(error => {
// ...handle error here, at least one operation failed...
});
I'd be doing it like this.
const promises = ids.map(
id => ( [axios.get(`blabla/${id}/email`), axios.get(`blabla/${id}/name`)] )
);
Promise.all(promises.map(Promise.all).then(
([email, name]) => {
console.log(email, name)
}
)).then(values => {
console.log(values);
});

Firebase Realtime Database - multiple sequencial fetch and write operations without nesting promises

I am using Firebase Realtime database for storage.
In order to update contact data for users when a "Note" is updated I need to perform some sequential operations. I wish to update/push contacts nodes to for each "affectedUser" that is included in the "Note".
Below is a crude representation of my database.
-notes
-note123456 <-- Note being updated
affectedUsers: {'L1234567890ABC': true, 'L0987654321XYZ': true} <-- affectedUsers
-users <-- Compose contact objects from here for all affectedUsers
-L1234567890ABC
name
alias
email
avatar
favouriteColour
-L0987654321XYZ
-contacts <-- Add new contacts here
-L1234567890ABC
-ABCDEFGHIJKLMNO0123 <-- Already added contact
alias
name
-L0987654321XYZ
-ABCDEFGHIJKLMNO0123 <-- Already added contact
My starting point is a list of "affectedUsers" that needs to be updated - a list of user Ids.
The desired, simplified, workflow looks like this
Iterate "affectedUsers" and compose "contact cards"
Then iterate all "affectedUsers" and add contact cards to each affectedUser
My current code
const dbRoot = snapshot.ref
affectedUsers = ['-L1234567890ABC', '-L0987654321XYZ']
let promises = [];
affectedUsers.forEach((affectedUser) => {
const ref = dbRoot.child(`users/${affectedUser}`)
promises.push(
ref.once('value', (userSnapshot)=>{
const userNodeData = userSnapshot.val()
const contactObject = {
alias: userNodeData.alias,
name: userNodeData.name
}
return contactObject
);
})
Promise.all(promises).then((contactObjects) => {
let updateContactsPromises = [] //Collect second promise chain
//Check contacts of affectedUsers
affectedUsers.forEach((userId) => {
const ref = dbRoot.child(`contacts/${userId}`)
updateContactsPromises.push(
ref.once('value', (updateUserContactsSnapshots) => {
updateUserContactsNodeData = updateUserContactsSnapshots.val()
//Remove userId from additions, prepare database update object, push data
//...
})
)
})
//Execute second, and last promise chain
Promise.all(updateContactsPromises) //Line 328
.then(()=>{
//...
})
.catch((err)=>{})
})
.then(()=>{
//...
})
.catch((err)=>{})
I realize nested promises is not a good thing - since I get warnings when performing a firebase deploy. ;)
328:9 warning Avoid nesting promises promise/no-nesting
328:9
warning Avoid nesting promises promise/no-nesting
✖ 2 problems (0 errors, 2 warnings)
How can I make sure my calls gets executed sequentially without nesting promises?
The problem is when you have a .then directly inside another .then. Usually, this can be fixed by returning the next Promise, instead of having a nested then. For example, change
prom.then(() => {
getAnotherProm().then(handleOther);
});
to
prom.then(() => {
return getAnotherProm()
})
.then(handleOther);
Here, you can return the second Promise.all to avoid nesting .thens:
Promise.all(promises)
.then((contactObjects) => {
let updateContactsPromises = [] //Collect second promise chain
//Check contacts of affectedUsers
affectedUsers.forEach((userId) => {
// ...
})
//Execute second, and last promise chain
return Promise.all(updateContactsPromises)
})
.then((updateContactsValues) => {
// handle resolve value of updateContactsPromises
})
.catch((err) => {
// handle errors
})
Remember to only .catch at a level where you can handle the error properly, and you can chain .thens together to avoid having to duplicate .catches.
You can also use .map instead of .forEach to construct the array of Promises all at once, eg:
const dbRoot = snapshot.ref
affectedUsers = ['-L1234567890ABC', '-L0987654321XYZ']
const affectedUserPromises = affectedUsers.map((affectedUser) => {
const ref = dbRoot.child(`users/${affectedUser}`)
return ref.once('value', (userSnapshot) => {
const userNodeData = userSnapshot.val()
return {
alias: userNodeData.alias,
name: userNodeData.name
};
});
});
Promise.all(affectedUserPromises).then((contactObjects) => {
// ...

Promises and race-conditions

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

Categories

Resources