Regarding nested Promises in NodeJS - node.js

There are many threads on this and using that i am trying to find solution for below problem.
getMethod() {
execDBQuery(sqlQuery1)
.then(productionQueryRows => {
prodResultFunc(resultSet) // Calling a function to get another set of DB values
.then (result) {
console.log(result) // Final result which will be sent to user
}
});
}
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
execDBQuery(sqlQuery2)
.then(resultSet => {
read value-1
})
execDBQuery(sqlQuery3)
.then(resultSet => {
read value-2
})
// Create a object using Value-1 & Value-2 and return object to map
});
);
}
I tried implementing below (with one just SQL execution) and it works fine.
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
execDBQuery(sqlQuery2)
.then(resultSet => {
read values using resultSet
Create object with resultSet values
})
return object
});
);
}
But i want use values for both SQLs (2 & 3) for creating object and this where i am struggling to find syntax for implementation. Any help/pointers to existing threads will be a great help.

Whenever you're dealing with complex Promise chains, I find that the async/await syntax is much easier to read. You end up with a lot less nested code.
The example below shows how you might combine the results of two different queries:
const prodRowSet = [1,2,3];
// Mock query function.
async function execDBQuery(sql) {
return [{ id: 1, query: sql }];
}
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
let resultSet2 = await execDBQuery('sqlQuery2');
let resultSet3 = await execDBQuery('sqlQuery3');
// Do whatever you want to combine the result sets.
let combinedObj = { resultSet2, resultSet3}
return combinedObj;
})
);
return results;
}
(async() => {
let output = await prodResultFunc(prodRowSet);
console.log("Output:", output);
})();

You might be looking for Promise.All
async function prodResultFunc(prodRowSet) {
const results = await Promise.all(
prodRowSet.map( async (objBatchRow) => {
Promise.all([execDBQuery(sqlQuery2), execDBQuery(sqlQuery3)]).then(function(values) {
//TODO:: Impement you business logic here
console.log(values);
});
});
);
}

Related

node using await in for loop which calls an async function

I'm having a little trouble getting back a result from an async function I am calling in a for loop, I should be getting back a string but it is returning a promise instead
I am following this syntax but I haven't been able to get it to work properly and I'm hoping for some insight into why this fails https://eslint.org/docs/rules/no-await-in-loop
here's the function I am trying to work with, in this case decodeTheObject is async and returns a promise but if I use await decodeTheObject eslint will give me an error saying I can't use await inside a for loop, unfortunately the workaround above still results in a promise being returned instead of a resolved value
async function decode (request, encodedObj) {
const decodedArr = [];
try{
for (const id of encodedObj) {
decodedArr.push({
data: decodeTheObject(request, id), // this is an async function
partial: true,
});
}
await Promise.all(decodedArr);
return decodedArr;
}catch (err) {
throwError(
req,
{
errorDetails: [
{
issue: 'INVALID_ENCODING',
description: 'Invalid encoded obj',
},
],
},
);
}
};
// ----- calling function -----
const decodedObj = await decode(request, encodedItem);
const payload = {
newprop: decodedObj
};
Promise.all() has to work directly on an array of promises. You are passing it an array of objects which won't know how to reach into the objects to get the promises so thus, it won't accomplish anything useful. There are a couple ways to approach this.
This will await each async call in sequence, get the value and push the value into the array:
async function decode (request, encodedObj) {
const decodedArr = [];
try{
for (const id of encodedObj) {
let data = await decodeTheObject(request, id);
decodedArr.push({data, partial: true});
}
return decodedArr;
} catch(e) {
...
}
}
Or, you could run them in parallel with Promise.all() like this by creating an array of promises and using Promise.all() on that array:
async function decode (request, encodedObj) {
return Promise.all(encodedObj.map(id => {
return decodeTheObject(request, id).then(data => {
return {data, partial: true};
});
})).catch(err => {
...
});
}

Reduce array of promises instead of mapping and filtering undefined/null values

Currently in our API-server code we have the following:
async someAsyncMethod() {
// ...
let products = await Promise.all(shopStructure.map(async entry => {
try {
const serviceResponse = await this.requestService.someService(entry);
return ProductMapper.map(entry, serviceResponse);
} catch (err) {
this.logger.error('...');
return undefined;
}
}));
products = products.filter(s => s);
return products;
}
How would I use reduce here in order to get rid of the filter operation?
I tried using the following:
async someAsyncMethod() {
// ...
const products = await Promise.all(shopStructure.reduce(async (accumulator, entry) => {
try {
const serviceResponse = await this.requestService.someService(entry);
accumulator.push(ProductMapper.map(entry, serviceResponse));
} catch (err) {
this.logger.error('...');
}
return accumulator;
},[]));
return products;
}
but this results in compile errors ("No overload matches this call", "Argument of type ... is not assignable to parameter of type <never>").
Maybe there's another elegant way to do this? Note that I deliberately use Promise.all instead of for...of for performance reasons.
I don't think reduce is the right tool here. You need to wait for n Promises to resolve, where n is the length of the shopStructure; the input array and the array of Promises that Promise.all needs are one-to-one, so .map and Promise.all definitely is the right approach.
You can avoid using .filter if you want by pushing to an array instead:
async someAsyncMethod() {
const products = [];
await Promise.all(shopStructure.map(entry => (
this.requestService.someService(entry)
.then(serviceResponse => products.push(ProductMapper.map(entry, serviceResponse)))
.catch(err => { this.logger.error('...'); })
)));
return products;
}
I don't think there's a good-looking way to use reduce, since the .map is already essential.

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.

NodeJS execute the next process after then() [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 3 years ago.
I'm trying to do upsert inside forEach becase the request is an array of objects using Sequelize.upsert method. I do this:
async createInfo (req, res){
newsData = req.body.news,
newsList = [];
newsData.forEach(async values => {
var news = {};
news.inserted_id = values.inserted_id;
if(news.inserted_id == null){
do {
news.inserted_id = crypto.getRandom(5);
var check = await InstitutionNews.count({where: {inserted_id: news.inserted_id}});
} while (check > 0);
}
News.upsert({
institution_id: institution_id,
inserted_id: news.inserted_id,
news_date: values.news_date,
news_title: values.news_title,
description: values.news_description,
created_by: created_by
}).then(ResNews => {
news.news_date = values.news_date;
news.news_title = values.news_title;
news.description = values.news_description;
newsList.push(news);
})
})
console.log("TEST")
}
but the process stop at the then(). It didn't execute the next code like the console.log.
Is there any way to execute next line code after the then(). I need then() because I wanna push the news object into newsList array. Because I need newsList as the if else conditional to do the next process.
Thanks.
Since it sounds like you need to wait for forEach to complete before you do another step, so I'd suggest using something like Promise.all:
async function createInfo(req, res) {
const newsData = req.body.news
const newsList = []
try {
await Promise.all(
newsData.map(
async (values) => {
const news = {}
news.inserted_id = values.inserted_id
// ...
News.upsert({...})
.then(ResNews => {
// ...
newsList.push(news)
})
}
)
)
console.log('newsList', newList)
// do other work
} catch (error) {
// handle errors appropriately
}
}
This way, you're creating an array of promises and waiting for all of them to resolve/finish.
Your current approach with forEach won't work in this case since it won't wait for each asynchronous call to finish before executing the "next" step. Since Promise.all returns a single Promise that you can then "wait" for to resolve before continuing with your next step.
Here's a simple example that somewhat does what you're trying to do:
async function createInfo() {
const newsData = [1, 2, 3, 4, 5]
const newsList = []
await Promise.all(
newsData.map(
async (values) => {
const temp = await Promise.resolve('temp')
console.log('first async call inside values of', values)
Promise.resolve('resolved')
.then((result) => {
newsList.push(`resolved with ${values}`)
})
}
)
)
console.log('newsList after')
console.log(newsList)
}
createInfo()
EDIT
Here's an alternative solution as rightly pointed by #Bergi in the comments:
async function createInfo(req, res) {
const newsData = req.body.news
try {
const newList = await Promise.all(
newsData.map(
async (values) => {
const news = {}
news.inserted_id = values.inserted_id
// ...
// since it doesn't look like you're using any
// data that you'd get back from the `News.upsert` call, wait for
// it to finish and just simply return your `news` object
await News.upsert({...})
news.news_date = values.news_date
news.news_title = values.news_title
news.description = values.news_description
return news
}
)
)
console.log('newsList', newList)
// do other work
} catch (error) {
// handle errors appropriately
}
}
use await instead of then like
await News.upsert({
institution_id: institution_id,
inserted_id: news.inserted_id,
news_date: values.news_date,
news_title: values.news_title,
description: values.news_description,
created_by: created_by
});
news.news_date = values.news_date;
news.news_title = values.news_title;
news.description = values.news_description;
newsList.push(news);

Sequelize nested promise on model create inside a loop

I have an async function (createObjects) that needs to create some models into the database, so, until all the objects are created (inside a forEach loop), the function needs to wait. After the last model is created, then it should return the "Data Synchronized!" string, but the "Data Synchronized!" message never waits for createObjects() to finish. I think I need to return all the model.create promises to the mainPromise, like an array of promises, but I don't know the best way to do that. Any suggestions?
PS: The calculateCost that is called inside createObjects is async and is working fine.
mainPromise()
.then( (data) => {
return proccessData(data); //this is a sync function
})
.then( (newData) => {
createObjects(newData); // this is a async function
})
.then( () => {
return "Data Synchronized!";
})
//this needs to be an async function
function createObjects(newData){
newData.forEach((bills) => {
//if the object bills has the Groups array attributes...
if (bills.Groups.length !== 0) {
//iterate through groups
bills.Groups.forEach( (group) => {
var uid = group.id;
var usage = group.Metric.UsageAmount;
var cost = calculateCost(usage, uid); //async function
//the problem is here
cost.then((result) => {
models.Billing.create({
group_id: uid,
cost: result,
usage: usage
});
});
})
}
});
}
var calculateCost = (usage, uid) => {
var cost;
return models.ObjectA.findOne({
where: { id: uid }
}).then((data) => {
if (data.type == "Interactive") {
cost = usage * 0.44;
} else if (data.type == "Automated") {
cost = usage * 0.11;
}
return cost;
});
}
There is nothing in your code watching the result of cost().then(...), so that bit of code is fire-and-forget. The same is true for your call to models.Billing.create and one of the thens toward the top of your code. That's why you're seeing the outcome you are. When using Promises, be on the lookout for places where you are creating promises and not returning them to a higher caller. This often suggests the creation of a promise that isn't being watched.
To fix this:
First of all, fix the then towards the top of the code so that the result of createObjects is actually being returned:
.then( (newData) => {
return createObjects(newData); // this is a async function
})
Better yet:
.then(createObjects)
After that's remedied...
Option 1 - use reduce instead of forEach
Use this approach if you want to assure that the queries are executed one at a time (in sequence), instead of all at once:
function processBillGroups(groups) {
return groups.reduce((last, group) => {
var group_id = group.id;
var usage = group.Metric.UsageAmount;
return last
.then(() => calculateCost(usage, group_id))
.then((cost) => models.Billing.create({ group_id, cost, usage }))
}, Promise.resolve());
}
function createObjects(newData) {
return newData.reduce(
(last, { Groups }) => last.then(() => processBillGroups(Groups)),
Promise.resolve(),
);
}
Option 1.1 Use async/await
This will also carry out the actions in sequence, but uses the async/await syntax instead of direct promise manipulation.
async function processBillGroups(groups) {
for (group of groups) {
let group_id = group.id;
let usage = group.Metric.UsageAmount;
let cost = await calculateCost(usage, group_id);
await models.Billing.create({ group_id, cost, usage });
}
}
async function createObjects(newData) {
for ({ Groups } of newData) {
await processBillGroups(Groups);
}
}
Option 2 - use map and Promise.all instead of forEach
Use this if you don't mind all of the actions executing at the same time (in parallel), or even prefer that they execute in parallel. createObjects will return a single promise that will resolve when all of the actions have completed:
function processBillGroups(groups) {
return Promise.all(groups.map((group) => {
var group_id = group.id;
var usage = group.Metric.UsageAmount;
return calculateCost(usage, group_id)
.then((cost) => models.Billing.create({ group_id, cost, usage }));
}));
}
function createObjects(newData) {
return Promise.all(
newData.map(({ Groups }) => processBillGroups(Groups))
);
}
Option 2.1 - Use map and Promise.all with a little bit of async/await:
Acts just like option 2, but the syntax is a little nicer.
function processBillGroups(groups) {
return Promise.all(groups.map(async (group) => {
let group_id = group.id;
let usage = group.Metric.UsageAmount;
let cost = await calculateCost(usage, group_id);
await models.Billing.create({ group_id, cost, usage });
}));
}
function createObjects(newData) {
return Promise.all(
newData.map(({ Groups }) => processBillGroups(Groups))
);
}

Resources