I have a list of IDs to query Mongo with which I am doing in a for loop and it always returns an empty []. Initially it kept returning promises so I moved from forEach to the standard for loop as you see here and toArray() both of which resulted in an empty [].
async function queryRoles(id) {
const db = await connectToMongo()
let response;
try {
response = await db.collection("permissions").find({"_id": xxx}).toArray();
console.log(await response);
} catch (error) {
console.log(error);
}
return response;
}
async function checkAuthorisation(list, groups) {
for (const item of list) {
const index = list.indexOf(item);
const rolesList = await queryRoles(item._id);
console.log(rolesList);
};
}
The rolesList always comes back in [] and is never populated with anything.
However, if I just update code with the same query for a single mongo document without using the for loop, the expected response comes back, so I know the connectivity to MongoDB is fine.
What am I doing wrong? Pulling my hair out at this point!!
Did you try this approach,
async function checkAuthorisation(list, groups) {
const roleList = await Promise.all(
list.map(async (item, index) =>
await queryRoles(item._id)
)
);
...
}
Related
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;
}
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 );
i'm trying to create an array which should have an items with open quotation. i have an Items DB in Mongo with key "open_quotation", if there is no quotation for this item => open_quotation: null. i want to map all the array items and to check if there is any open quotations for this item, if there are an open quotation - i want to push it to another array, whihc I created, and to send to client. the problem that when i do map method on items array it is an async function and i get an array already after that the response has been send... so the client receive it as an empty array. pls help to solve this problem.
const{items}= req.body;
const quotations=[];
items.map(async item=>{ const i=await Item.findOne({_id: item._id})
if(i){
`if(i.openquotation){ quotations.push(i)}`
return
)}
res.send({response: true, quotations:quotations})
`````
Can you try that:
try {
const response = await Promise.all(items.map(item => Item.findOne({_id: item._id})));
const quotations = response.filter((elem) => !!elem.openquotation);
res.send({ response: true, quotations });
} catch (error) {
res.send({ response: false, error });
}
you can use async and q module for find query in loop
when you run mainFunction(), mainFunction calls getData()
let async = require('async');
let q = require('q');
const mainFunctio = async()=>{
const{items}= req.body
console.log("start")
resultFromFindLoob = await getData(items)
console.log("finish");
console.log(resultFromFindLoob)
}
const getData = async (items)=>{
let defer =q.defer();
let result = []
async.eachSeries(items , async(item)=>{ // like a for
try {
let i = await Item.findOne({_id: item._id}).lean();
//do something for result (processing)
if(i)
result.push(i)
} catch (error) {
console.log(error)
}
},()=>{ //callback when finish loop
console.log("finish findone() loop")
defer.resolve(result)//return the result
})
return defer.promise
}
mainFunctio()
restult of mainFunction() based on console.log
1.start mainFunction
2.start getData
3.finish find loop getData
4.finish mainFunction
5.final result : .....
I want to send an order-confirmation email(with nodemailer) to customers when they finished the order.
In this confirmation email I want to insert some data from the buyed products.
I've used many async/await combinations but none of them succeeded.
The code I got for now:
async function getProductData(products) {
let productsArray = products;
try {
let productsHtml = "";
for await (const product of productsArray) { // loop through the products
db.collection("collectionname").findOne(query, (err, data) => { // get data for every product
if (err) console.log(err)
else {
let productHtml = `<p>productname: ${data.productname}</p>
<img src="${data.imageSrc}">`;
productsHtml += productHtml; // add the product html string to productsHtml
}
})
}
} catch (error) {
console.log(error);
}
return productsHtml; //return the productsHtml string. But it returns "", because it doesn't await the mongodb function
}
async function sendConfirmationMail() {
let productsHtml = await getProductData(products); //"products" is an array
//nodemailer code
let transporter = nodemailer.createTransport({
// configuration
});
let email = await transporter.sendMail({
//email information
html: `<p>Thank you for your order!</p>
${productsHtml}`;, // insert the html string for all products into the mail
});
}
The problem is that the return statement in getProductData() fires before the for loop finishes. And then returns undefined.
Does anyone have tips on how I can implement async/await the correct way here?
You are not awaiting your database call. You are passing it a callback, so it does not return a Promise :
.findOne(query, (err, data) => {
This has a callback. You are passing it a function as second argument, that takes (err,data) as its own arguments. You need to remove this callback and use the async/await syntax with try/catch. Something like
const data = await db.collection("collectionname").findOne(query);
On the other hand you don't need to for await (const product of productsArray). You are iterating an array of static values, not an array of Promises. Remove this await.
I have a JSON file from Spotify with objects that represent artists. Inside of those objects, one of the properties is 'genres' that is an array of strings (genres).
What I'm trying to do is find or create those genres in MongoDB. Then add those object IDs into an array and pass that array when I create my Artist objects.
But, my Artists get created in the DB way before that find or create process for their genres is done.
Thus the line fillRest runs before fillGenres.
I've tried changing from Object.values.forEach to for..of, for..in, different asyncs, promises, modulating code...
Basically added await everywhere I can (most in the wrong places probably)
import * as data from '../spotify_data/artist_id.json';
async function fillGenres(array) {
const genreIDs = []; // array of genre object IDs I'm trying to put inside every appropriate artist
if (array !== 'undefined') {
for (const element of array) {
await Genre.findOrCreate({ name: element }, (err, result) => {
genreIDs.push(result._id);
});
}
}
return genreIDs;
}
async function fillRest(entry, genreIDs) {
const artist = {
name: entry.ArtistName,
genres: genreIDs,
spotifyID: entry.ArtistID,
popularity: entry.Popularity,
spotifyFollowers: entry.Followers,
};
Artist.create([artist])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
}
async function spotifySeed() {
const entries = Object.values(data);
for (const entry of entries) {
fillGenres(entry.Genres)
.then((genreIDs) => {
fillRest(entry, genreIDs); // this line gets executed before fillGenres above^ which is super weird
});
}
}
spotifySeed();
Artists get added to MongoDB with genres set as [].
After that I get console output with good genreID arrays (that were supposed to be inside there^ instead of genres).
Resolved - EDIT
Thanks to everyone who helped. The problem was in findOrCreate as it did not return a promise. I used this package for mongoose instead that has Promises (https://www.npmjs.com/package/mongoose-findorcreate).
And the code now is
if (Array.isArray(array)) {
// eslint-disable-next-line no-restricted-syntax
for (const element of array) {
await Genre.findOrCreate({ name: element })
.then((result) => {
genreIDs.push(result.doc._id);
});
}
}
and in SpotifySeed
const genreIDs = await fillGenres(entry.Genres);
await fillRest(entry, genreIDs);
I haven't used the Spotify APIs before, so I can't say much about that but there are a couple of issues I see at first glance. First, you're checking if (array !== 'undefined') {, which is checking if the array variable is a string that is literally 'undefined' (not the value undefined). I'm fairly certain that is not what you intended. You would be better off using Array.isArray(array) here, if you're wanting to make sure array is actually an Array.
Second, you're using async functions and Promises mixed together, which (imo), you generally shouldn't do. You should use one or the other, so it's consistent and easier to follow. If you use await instead of .then, you will be able to write it in a more "synchronous" fashion and it should be easier to understand.
import * as data from '../spotify_data/artist_id.json';
async function fillGenres(array) {
const genreIDs = []; // array of genre object IDs I'm trying to put inside every appropriate artist
if (Array.isArray(array)) {
for (const element of array) {
const result = await Genre.findOrCreate({ name: element });
genreIDs.push(result._id);
}
}
return genreIDs;
}
async function fillRest(entry, genreIDs) {
const artist = {
name: entry.ArtistName,
genres: genreIDs,
spotifyID: entry.ArtistID,
popularity: entry.Popularity,
spotifyFollowers: entry.Followers,
};
try {
const result = await Artist.create([artist]);
console.log(result);
} catch (error) {
console.log(error);
}
}
async function spotifySeed() {
const entries = Object.values(data);
for (const entry of entries) {
const genreIDs = await fillGenres(entry.Genres);
await fillRest(entry, genreIDs);
}
}
await spotifySeed();
I don't know anything about the Spotify API, so this is just a guess. In fillGenres, you have:
await Genre.findOrCreate({ name: element }, (err, result) => {
genreIDs.push(result._id);
});
You're passing a callback function. Sometimes libraries will allow you to use promises or callbacks. If you pass a callback, it won't return a promise. So I'm guessing that the loop starts off all the calls to Genre.findOrCreate, which doesn't return a promise since you're using a callback. and then immediately returns. Then fillRest is called and may finish before all the Genre.findOrCreate calls.
You want something like:
const result = await Genre.findOrCreate({ name: element });
genreIDs.push(result._id)
Though even better would be this:
function fillGenres(genreNames) {
if(!genreNames || !genreNames.length) return Promise.resolve([])
return Promise.all(genreNames.map(name => {
return Genre.findOrCreate({ name })
.then(result => result._id)
})
}
This will run all the genre calls at the same time and return them all when they're done, rather than waiting to add one after another (as in your for loop).
If Genre.findOrCreate does not return a Promise, you could create a version that does:
function genreFindOrCreate(data) {
return new Promise((resolve, reject) => {
Genre.findOrCreate(data, (err, result) => {
if(err) reject(err)
else resolve(result)
})
})
}