sequential request in node js - node.js

I'm learning to make an API in node js with express library
i have two things : product , order
and each order has an address , one or more product and a total price which is based by price of products
when i try to save an order i just pass the products IDs so in code i should get the products and sum their prices plus i want to show the products in the response so what i did was to put the ids in an array and i executed a foreach loop in array and in every loop i get the product and push it in an array .
the problem is that the response (actually every code after foreach loop) is executed before i get the products,
i have searched about Async/Await and i tried to implement it but it didn't work
my code is like this
function getproductpromis(options){
return new Promise((resolve,reject) =>{
http.get(options, resolt =>{
let data ;
resolt.on('data', d => {
data +=d;
})
resolt.on('end', f =>{
resolve (data);
})
resolt.on('error', e =>{
reject(e);
console.log(e);
})
});
});
}
async function getproduct(element){
let object;
const options = {
hostname:'localhost',
port:5000,
path:'/products/'+element,
mathod:'GET'
}
const request_call = await getproductpromis(options);
console.log('request_call');
console.log(request_call);
return request_call;
}
router.post('/',(req,res,next) => {
let prod;
console.log(req.body.products);
const ids = req.body.products;
let totalprice = 0;
let objects =[];
ids.forEach(element => {
prod = getproduct(element);
console.log('prod');
console.log(prod);
objects.push(prod);
});
res.status(200).json({
objects
});
}
and here is my log:
[ '5dab2706baab4b1e90dc30ff', '5dab2792f0662d4f54bbf1f1' ]
prod
Promise { <pending> }
prod
Promise { <pending> }
POST /orders 200 56.974 ms - 19
{ _id: 5dab2706baab4b1e90dc30ff,
name: 'langero',
quantity: 5,
price: 1500,
__v: 0 }
GET /products/5dab2706baab4b1e90dc30ff 200 208.973 ms - 114
request_call
undefined{"massage":"product found","product":{"ID":"5dab2706baab4b1e90dc30ff","name":"langero","quantity":5,"price":1500}}
{ _id: 5dab2792f0662d4f54bbf1f1,
name: 'langer',
quantity: 5,
price: 1500,
__v: 0 }
GET /products/5dab2792f0662d4f54bbf1f1 200 1172.254 ms - 113
request_call
undefined{"massage":"product found","product":{"ID":"5dab2792f0662d4f54bbf1f1","name":"langer","quantity":5,"price":1500}}
and response i get in postman
response

Change your forEach loop to for...of loop
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of]
Here is your updated router.post using async/await and for...of loop
router.post('/',async (req,res,next) => {
let prod;
console.log(req.body.products);
const ids = req.body.products;
let totalprice = 0;
let objects =[];
for (let element of ids) {
prod = await getproduct(element);
console.log('prod');
console.log(prod);
objects.push(prod);
}
res.status(200).json({
objects
});
}

So you want to execute and an array of Promises, you're on the right track but may I point you in the direction of Async/Await. Here I marked your router.post callback as async so I can use await. I use Array.map to return an array of promises that get passed to Promise.all
router.post("/", async (req, res, next) => {
const ids = req.body.products;
const objects = await Promise.all(
ids.map((id) => {
return getproduct(id);
})
);
res.status(200).json({
objects
});
});

Related

Should I do database change inside each fetch request, or inside Promise.all?

I want to loop through the first 151 Pokemon using the PokeAPI, and adding each Pokemon to my mongo database.
I already have the schema for the pokemon, where I am just saving their string name, and an array of their moves.
I am looping through the axios calls, and storing them into an array of promises, and waiting for them to resolve
app.get('/', async (req, res) => {
const promises = []
for (let i = 1; i <= 151; i++) {
promises.push(axios.get(`https://pokeapi.co/api/v2/pokemon/${i}`))
}
await Promise.all(promises).then(async (p) => {
const newPokemon = new Pokemon({
name: p.name,
moves: p.moves,
})
await newPokemon.save()
})
})
Is this at all correct? Where should I be doing my database queries, inside the individual axios calls or inside the promise.all?
Promise.all returns an array of values. You should use the insertMany function to insert all Pokemon at once:
await Promise.all(promises).then(async (pokemons) => {
try {
await Pokemon.insertMany(pokemons);
} catch (err) {
console.error(err);
}
})

Pushed object in forEach is empty at the map look end [duplicate]

This question already has an answer here:
Returning value from async function node.js
(1 answer)
Closed 1 year ago.
I'm looking to push object from a forEarch, after getting data inside forEarch, the console.log show me correct value and object are properly pushed to the array.
But at the end of the forEach, if I try to access to the object pushed, there is no object inside the array
Code below
async function get_line_items(items) {
line_items2 = [];
await items.forEach((element, index) => {
const id = element.id;
if (id) {
await getPriceFromCartCheckout(id, (err, results) => {
if (err) {
console.log(err);
return;
}
if (results && !isEmpty(results)) {
// Add new item
line_items2.push({
name: element.name,
amount: results[0].price,
currency: "eur",
quantity: element.quantity,
})
}
console.log(line_items2) // => Here objects are properly added to the array on each loop
});
}
})
console.log(line_items2) // => Here the array is empty.
return line_items2;
}
const line_items = await get_line_items(req.body[1].items);
console.log( line_items); // => Here the array is empty.
module.exports = {
getPriceFromCartCheckout: async (id) => {
return await pool.query(`SELECT volume, price FROM products WHERE id = ?`, [
parseInt(id)
])
.then(iuidtest => {
return iuidtest;
});
},
};
Any help would be appreciate :)
Found the solution here :
NodeJS async MySQL call to connection pool returns no result
You should await getProductIdWeightPrice and not the loop itself. The loop does what it's supposed to do - looping the array, calling some external methods. What those methods do - the loop does not care.
In order to "pause" the loop, you need to await the async call to get the data. Do that and it will work :)

Only first document is returned as response instead of multiple documents using Firestore and Cloud Functions

I have this Express function:
exports.getSliderTipsteriData = (req, res) => {
let sliderTipsteriData = [];
db.collection("tipsterBanner")
.orderBy("createdAt", "desc")
.where("show", "==", true)
.get()
.then((data) => {
data.forEach((doc) => {
let eventId = doc.data().eventId;
sliderTipsteriData = doc.data();
db.collection("evenimenteTipsteri")
.orderBy("createdAt", "desc")
.get()
.then((data) => {
sliderTipsteriData.tipsteri = [];
data.forEach((doc) => {
if(doc.data().bilet[0].id === sliderTipsteriData.eventId) {
sliderTipsteriData.tipsteri.push({
tipster: doc.data().tipster,
homeTeam: doc.data().bilet[0].homeTeam,
awayTeam: doc.data().bilet[0].awayTeam
})
} else null
})
return res.json(sliderTipsteriData);
})
})
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code });
});
};
and received this as response:
{
"imageUrl": "https://firebasestorage.googleapis.com/v0/b/socialape-bea5b.appspot.com/o/slider1.jpg?alt=media&token=0824a93d-4bc3-49fa-9ae8-4408961a0736",
"event_date": 1614110400,
"awayTeamName": "Bayer Leverkusen",
"awayTeamPercent": 23,
"homeTeamName": "Atletico Madrid",
"homeTeamShortName": "ATL",
"awayTeamEmblem": "https://media.api-sports.io/football/teams/34.png",
"createdAt": "2021-03-22T18:25:03.667Z",
"homeTeamEmblem": "https://media.api-sports.io/football/teams/49.png",
"awayTeamShortName": "LEV",
"homeTeamPercent": "77",
"show": true,
"eventId": 652238,
"homeTeamColor": "#0099ff",
"awayTeamColor": "#ff0000",
"etapa": "Liga Campionilor, Etapa 2",
"tipsteri": [
{
"tipster": "daniel",
"homeTeam": "Lazio",
"awayTeam": "Bayern Munich"
},
{
"tipster": "user",
"homeTeam": "Lazio",
"awayTeam": "Bayern Munich"
}
]
}
The problem is that I have more than one document in tipsterBanner collection, but I receive only the first one. So the forEach doc might not be working properly.
Any idea what I miss here?
I expect to receive as response the sliderTipsteriData array with multiple objects, not only the first one. Looks like the forEach actually doesn't loop.
This is because you have promises trying to run in loops that don't wait for them. Inside your then blocks, you perform more asynchronous calls (collection().get()) but your code isn't waiting for them to resolve, so it's just flying through your forEach loop, creating those promises, but then getting to the end and returning.
There are two ways to solve that problem - (1) put your then/catch calls into a Promise.all and wait for that to resolve or (2) switch to async/await. But I think you have an even better solution. Right now, you're querying your evenimenteTipsteri collection in each loop, but you're not using any information from the tipsterBanner collection as parameters in the evenimenteTipsteri query, so you could just query both of them one time and then handle all the filtering/organizing in code. This will help speed up your results and help protect you from the costs of unnecessary reads on Firestore.
This code is untested because I just copy/pasted your code and rewrote it without being able to run it, but this is the main idea:
exports.getSliderTipsteriData = (req, res) => {
let sliderTipsteriData = [];
// These create the promises which will resolve in our Promise.all()
const tipsterBanner = db.collection("tipsterBanner").orderBy("createdAt", "desc").where("show", "==", true).get()
const evenimenteTipsteri = db.collection("evenimenteTipsteri").orderBy("createdAt", "desc").get()
Promise.all([tipsterBanner, evenimenteTipsteri]).then((results) => {
// results is now an array of your original "data" items from your then blocks
// results[0] is the data from the tipsterBanner query
// results[1] is the data from the evenimenteTipsteri query
const tipsterBannerResults = results[0] // Just to make it more readable
const evenimenteTipsteriResults = results[1] // Just to make it more readable
tipsterBannerResults.forEach(doc => {
let eventId = doc.data().eventId
sliderTipsteriData = doc.data() // Is this right? You could end up overwriting your data
sliderTipsteriData.tipsteri = []
evenimenteTipsteriResults.forEach(doc => {
if(doc.data().bilet[0].id === sliderTipsteriData.eventId) {
sliderTipsteriData.tipsteri.push({
tipster: doc.data().tipster,
homeTeam: doc.data().bilet[0].homeTeam,
awayTeam: doc.data().bilet[0].awayTeam
})
}
})
})
return res.json(sliderTipsteriData)
}).catch(error => {
// Handle errors from your queries
})
}

Express returns empty array

I currently have the following code
router.get('/uri', (request,response) => {
let final = [];
TP.find({userID: request.userID})
.then(tests =>{
tests.forEach(test => {
A.findById(test.assignmentID)
.then(assignment => {
final.push({
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
})
})
.catch(error => {
console.log(error)
})
})
return response.send(final);
})
.catch(err => {
console.log(err);
return response.sendStatus(500);
})
})
The code is supposed to query 2 MongoDB databases and construct an array of objects with specific information which will be sent to the client.
However, I always get an empty array when I call that endpoint.
I have tried making the functions async and make them wait for results of the nested functions but without success - still an empty array.
Any suggestions are appreciated!
forEach doesn't care about promises inside it. Either use for..of loop or change it to promise.all. The above code can be simplified as
router.get('/uri', async (request,response) => {
const tests = await TP.find({userID: request.userID});
const final = await Promise.all(tests.map(async test => {
const assignment = await A.findById(test.assignmentID);
return {
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
};
}));
return response.send(final);
});
Hope this helps.

Node.js Sequalize creating new rows in forEach loop

I'm trying to create new rows in the database using Sequalize ORM. I receive an array of collections from req.query.collections. For each of those collections I need to create a new userCollection. If none userCollections were created, I wanna respond with internal server error (line 41), otherwise return an array of objects with newly created userCollections.
The problem is, I keep getting an internal server error when I make test requests from Postman. When I check my database, I see that those userCollections were created, so no error occurred.
I know why this happens: because userCollection.build({ stuff }).save() returns a promise. So when I try to console.log userCollections from within .then() statement, I get an array with a newly created collections, just like I should. But by that time server has already responded with internal server error.
Here's my function code:
exports.addCollections = async (req, res, next) => {
const libraryId = req.params.libraryId;
const collections = req.query.collections;
if (!collections)
next(Boom.forbidden());
const userCollections = [];
collections.forEach(async (collectionId, index) => {
const collection = await Collection.findByPk(collectionId);
if (!collection)
return next(Boom.notFound());
userCollection.build({
user_id: req.user.id,
library_id: libraryId,
public_collection_id: collection.id,
title: collection.title,
description: collection.description
})
.save()
.then(newUserCollection => {
userCollections.push(newUserCollection.get({ plain: true }));
// should be printed first, but comes second
// prints out the array with newly created record
console.log(userCollections);
})
.catch(error => {
console.log(error);
});
});
// should be printed second, but comes first
// prints out empty array
console.log(userCollections);
if (userCollections.length === 0) {
next(Boom.internal());
}
res.json(userCollections);
}
Posting the solution
Thanks to Sebastien Chopin who created this tutorial:
https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
So I added this function:
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
And instead of collections.forEach...(blah blah) (line 10 of the code posted in the question) I do:
try {
await asyncForEach(collections, async (collectionId, index) => {
const collection = await Collection.findByPk(collectionId);
if (!collection)
return next(Boom.notFound());
userCollection.build({
user_id: req.user.id,
library_id: libraryId,
public_collection_id: collection.id,
title: collection.title,
description: collection.description
})
.save()
.then(newUserCollection => {
userCollections.push(newUserCollection.get({ plain: true }));
console.log(userCollections);
})
.catch(error => {
console.log(error);
});
})
} catch (err) {
return next(Boom.internal(err.message));
}

Resources