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);
Related
I have an array of addons and I want to insert them into the db table.
var addons = [sample,sample,.....]
return new Promise((resolve,reject) => {
addons.foEach(async addon => {
// first check if the items is in db
const response = await Kinex.where({}).from('table_name');
if(response.length == 0){
// insert new record
const insertResp = kinex('table_name').insert(addon)
addon.system_id = insertResp[0];
}else{
addon.system_id = response[0].id;
}
})
})
What I expected is to have unique record in the database, but the above code produced duplicate record in the database. Please help to find out the issue with the code.
The problem is running async function inside a loop. As mentioned by #Felix, forEach doesn't know about async functions and doesn't wait for your where query to return. If you wanna do things in async manner inside loops, you can do it with for..of loops. Also make sure to always use try/catch blocks while using async/await. Below is the code in your case:
const addons = [sample,sample,.....];
return new Promise(async (resolve, reject) => {
try {
for (let addon of addons) {
// first check if the items is in db
const response = await Kinex.where({}).from('table_name');
if (response.length) {
const insertResp = await kinex('table_name').insert(addon)
addon.system_id = insertResp[0];
} else addon.system_id = response[0].id;
resolve(); // resolve with whatever you wants to return
}
} catch (e) {
reject(e)
}
});
You can read more on for..of with async/await here.
As pointed by #Sándor, here's the code using Promise.all:
var addons = [sample, sample, .....]
return Promise.all(addons.map(async addon => {
// Do your async stuff here
// first check if the items is in db
const response = await Kinex.where({}).from('table_name');
if (response.length == 0) {
// insert new record
const insertResp = kinex('table_name').insert(addon)
addon.system_id = insertResp[0];
} else {
addon.system_id = response[0].id;
}
}))
i have a question about using async await inside another promise. I have a function call another function to get a transaction details.
When i running the function LastTransactions the field details do not show results. Anyone can help me ?
LastTransactions: async (transactionKey, page) => {
const api = `https://api.pagar.me/1/payables?recipient_id=${transactionKey}&count=${totalResults}&page=${page}&api_key=${PagarmeApiKey}`;
const response = await axios.get(api);
transactions = response.data.map((item) => {
return {
id : item.id,
transactionId : item.transaction_id,
trxDetails : [transactionDetails(item.transaction_id)],
}
});
return transactions;
},
and a detail function
async function transactionDetails(id){
const response = await axios.get(`https://api.pagar.me/1/transactions/${id}?api_key=${PagarmeApiKey}`)
const data = response.data;
return data;
}
You need to utilize the Promise.all method to take an array of promises and return an array with your transactions once each individual call for transaction details finishes.
async (transactionKey, page) => {
const api =
`https://api.pagar.me/1/payables?recipient_id=${transactionKey}&count=${totalResults}&page=${page}&api_key=${PagarmeApiKey}`;
const response = await axios.get(api);
// create an array of promises and wait for
// all of them to resolve before continuing
const transactions = await Promise.all(
response.data.map(async item => {
const { id, transaction_id } = item;
// get transaction details for each item in the array
const trxDetails = await transactionDetails(transaction_id);
return {
id,
trxDetails,
transactionId: transaction_id,
};
})
);
return transactions;
};
References:
Promise.all() - MDN
Since transactionDetails(item.transaction_id) is Asynchronous, you need to await that as well, otherwise it will return immediately and trxDetails will contain a promise object, and not response.data.
try this:
transactions = response.data.map(async (item) => {
return {
id : item.id,
transactionId : item.transaction_id,
trxDetails : [await transactionDetails(item.transaction_id)],
}
});
If I didn't need to worry about asynchronous code, this is what it'll look like:
app.get('/user/:username/events', async (req, res) => {
// Grab all events by that user
let events = await axios.get(`${baseApiUrl}/users/${req.params.username}/events`, options).then(d => d.data)
// Loop through events
events.forEach(event => {
// If event type is A_Event
if (event.type === 'A_Event') {
// Loop through objArr array
event.payload.objArr.forEach(async obj => {
// Grab additional data through endpoint
// that lives in obj
let data = await axios.get(obj.url)
// Append data back onto obj
obj.objData = data
})
}
})
// return events obj with objData appended to each obj within
// objArr
res.json({ events })
})
But this doesnt work because it returns events before its done grabbing all the data.
I tried doing something with Promise.all but couldnt get what I needed to get.
events.map(async event => {
if (event.type === 'A_Event') {
getData(event.payload.objArr)
.then(objData => {
// This returns an array of objData that
// then needs to be added back to the specific obj
// it came from
event.payload.objArr.forEach((obj, i) => {
obj.objData = objData[i]
})
})
}
res.json({ events })
})
const getData = async objArr => {
const requests = objArr.map(async obj => {
const data = await axios.get(obj.url, options)
return {
stats: data.data.stats,
created_at: data.data.created_at
}
})
return Promise.all(requests)
}
Would really appreciate the help with this. Been stuck on this problem for a good bit.
EDIT
Tried switching to a for of loop according to this post: Using async/await with a forEach loop... No luck but I think its a step in the right direction.
events.forEach(async event => {
if (event.type === 'A_Event') {
for (let obj of event.payload.objArr) {
let objData = await axios.get(obj.url, options)
obj.objData = objData
console.log('setting value')
}
}
})
res.json({ events })
console.log('returned value')
I get returned value before I get setting value...
Promise.all(requests);
Will return a promise so you can handle it somewhere else like this:
getData(objaArr).then((result) => {
// your code
});
In your second block of code, just await for Promise.all()
return await Promise.all(requests);
You would use Promise.all as suggested in the other answer(s).
To retrieve the result you would do something like:
let [languages, currencies, countries] = await Promise.all([
this.downloadLanguages(),
this.downloadCurrencies(),
this.downloadCountries()
])
Your variables can be assigned values returned from the promises using this call structure.
Got it working. I needed to switch the initial loop to a for of. Not just the inner loop. Thanks everyone!
for (let event of events) {
if (event.type === 'A_Event') {
for ( let obj of event.payload.objArr) {
let objData = await axios.get(obj.url, options)
obj.objData = objData.data
}
}
}
You could use good old-fashioned for loops:
for (let i = 0; i < events.length; i++) {
if (events[i].type === 'A_Event') {
for (let j = 0; j < events[i].payload.objArr.length; j++) {
const data = await axios.get(events[i].payload.objArr[j].url);
events[i].payload.objArr[j].objData = data;
}
}
}
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);
});
});
);
}
I am having some issues with Async/Await when trying to process my MSSQL Query.
The query works fine because if i do
console.log(sqltest.getNextId("A"))
I get the expected result. But i need to wait for the result from first query to pass on to second one so i tried below. Am i missing here something ? I would like to keep it as clean as possible because i might have the need to execute more then 2 in Sequence.
async function someMethod() {
var newId = await sqltest.getNextId("A")
var nextId = await sqltest.updateId("A",newId)
console.log(newId + ' - ' + nextId)
}
someMethod()
Here is what my getNextId looks like
const getNextId = (counter_id) =>
{const params = [{ name: "p_counter_id", type: sql.VarChar(10), value: counter_id }]
sqlUtil
.storedProcedure(params, "sp_counter_sel")
.then(result =>
{
var newCounter = sequence
(
result.recordset[0].next_id,
result.recordset[0].counter_length,
result.recordset[0].counter_fill
)
console.log(newCounter)
return newCounter
}
)
.catch(err => {
console.log('Error: ' + err.message)
})
}
ok to eliminate all confusion here is my .storedProcedure code
let storedProcedure = async (params, storedProcedureName) => {
const pool = await getOrCreatePool()
let request = await pool.request()
params.forEach((parameter) => {
parameterDirection = parameter.isOutput ? 'output' : 'input';
request = request[parameterDirection](parameter.name, parameter.type, parameter.value)
})
try {
return await request.execute(storedProcedureName)
sql.on('error', err => {
})
} catch(err) {
let message = {
message: {
msg: err.message,
number: err.number
},
}
throw message;
}
}
There is no underhanded error it just returns undefined - undefined for the console.log
This comment is actually is the important bit!
getNextId does not actually return anything, not the value you are looking for, and not a promise that you can await. The reason you are seeing output is that getNextId runs a console.log in the then callback.
But console.log(sqltest.getNextId("A")) is probably spitting out an undefined
Since sqlUtil.storedProcedure seems to return a promise (you can tell because you call then() on it, you should be able to convert this to an async function, and await on that.
const getNextId = async (counter_id) => {
const params = [{
name: "p_counter_id",
type: sql.VarChar(10),
value: counter_id
}]
const result = await sqlUtil.storedProcedure(params, "sp_counter_sel")
var newCounter = sequence(
result.recordset[0].next_id,
result.recordset[0].counter_length,
result.recordset[0].counter_fill
)
return newCounter
}
console.log(await getNextId("A")); // Should be what you expect.
This function now has a return value. Because it is now async and has a return statement that is not nested inside another function. It now returns a Promise that resolves to newCounter, and can be called with await.