Async await in for loop - javascript

I have this function and I'm trying to push objects into the "groupData" array and then return the response object but when the function successfully runs, I get "response" as a null object. What is wrong with my code can anyone help? How can I make the function to wait for the map function to finish and then return.
const groupList = async (io, socket, userid) => {
var response = {};
try {
var groupData = [];
ddb.get({
TableName: "Tablename",
Key: { Username: userid },
})
.promise()
.then(async (user) => {
if (Object.keys(user).length === 0) {
} else {
const groups = user.Item.Chatgroups;
groups.map((g) => {
ddb.get({
TableName: "Tablename",
Key: { ChatID: g },
})
.promise()
.then(async (data) => {
groupData.push({
ChatID: g,
Chatname: data.Item.Chatname,
Group: data.Item.Group
});
})
.catch((err) => {
console.log("Chat group not found");
});
})
response["groups"] = groupData;
}
})
.catch((err) => {
response["code"] = 400;
response["message"] = "Something Went Wrong";
});
} catch (error) {
console.log(error);
} finally {
return response;
}
};

Use Promise.all and if you use async then make use of await.
Here is how your code could look. I removed the error handling -- first test this and when it works, start adding back some error handling (with try/catch):
const groupList = async (io, socket, Username) => {
const user = await ddb.get({
TableName: "Tablename",
Key: { Username },
}).promise();
if (!Object.keys(user).length) return {};
return {
groups: await Promise.all(user.Item.Chatgroups.map(async ChatID => {
const { Item: { Chatname, Group } } = await ddb.get({
TableName: "Tablename",
Key: { ChatID },
}).promise();
return { ChatID, Chatname, Group };
}))
};
};

I searched too long for this 😭
for await (item of items) {}

Related

How to refactor an async function with Promise inside of it

Having this async function that returns a Promise:
async function doSomething(userId) {
return new Promise(async (resolve, reject) => {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return resolve({
rooms,
success: true,
paginator: {
// some data related to pagination
},
});
}
});
});
}
I'm not sure if it really needs to contain new Promise inside as it is already declared as async function. Is it mandatory in this case?
Because when that part was removed and at the end instead of return resolve({...}) it is only return {...} it seems to not settle.
Here is the modified code with new Promise and different return:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
}
This method is used somewhere else in this way:
const myInfo = await someObj.doSomething('myUserId');
and then checked:
if (myInfo.success) { ... }
For the first way of writing the function it works fine, for the second one myInfo is undefined and it throws and error that cannot read success of undefined.
Is something missing from the implementation?
For the second version I can see that you actually not returning anything from doSomething so I think you should do the below:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
const obj = Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
return obj;
}
In general, you don't need to explicitly return a Promise in an async function as by default the returned value will be wrapped in a Promise , whatever it is. You just need to return something
Room.aggregatePaginat is written in coninuation-passing style, which does not interface well with promises. A generic promisify function can be used to convert any continuation-passing style function into a promise-based one. If you don't wish to write this function yourself, it is provided by Node as util.promisify -
const promisify = f => (...args) =>
new Promise((resolve, reject) =>
f(...args, (err, data) => err ? reject(err) : resolve(data))
)
Now it's easy to refactor your doSomething. Note use of Promise.all means all rooms data is processed in parallel rather than in series -
async function doSomething(userId) {
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
success: true,
paginator: ...
}
}
Also you should avoid things like { success: true } because a resolved promise is inherently "successful". A rejected one is not.
And watch out for return occurring after a throw. In this case the return is unreachable so it doesn't do what you think it's doing.
if (err || !data) {
throw err;
return { // unreachable
success: false, // these four lines
rooms: [], // are completely ignored
}; // by javascript runtime
}
Again, { success: false } goes against the Promise pattern anyway. If you want to recover from a rejection and recover with an empty list of { rooms: [] }, do it like this instead -
doSomething(user.id)
.catch(err => ({ rooms: [] }))
.then(res => console.log(res.rooms))
Better yet, you can try..catch inside doSomething and return the appropriate empty response in the event of an error. This prevents the error from bubbling up and forcing the user to handle it -
async function doSomething(userId) {
try { // try
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
paginator: ...
}
}
catch (err) { // catch
return { rooms: [] }
}
}
doSomething(user.id)
.then(res => console.log(res.rooms))

When I call a function in a Promise it doesn't work synchronous in ExpressJS

I'm new at Node and what I'm trying to do here is call a function before the page rendered but my functions work after my page rendered and I can't get data coming from another function.
exports.getCity = (req, res) => {
controlCity(req.params.city).then((control) => {
res.render('index.ejs', control);
})
}
const controlCity = async (cityStub) => {
return new Promise((resolve, reject) => {
City.findAll({ where: { SEHIRSTUB: cityStub } })
.then(
city => {
if (city.length === 0) { // when it comes here it works
resolve({ value: "test" });
}
else {
resolve(controlPredictedWeather(city[0]));
}
}).catch(err => console.log(err));
}
)
}
const controlPredictedWeather = city => {
Predicted_Weather.findAll({ where: { CITYID: city.ID } }).then(
degree => {
if (degree.length === 0) {
return (getPredictedWeather(city)); // it goes another function
}
else {
console.log({ value: degree[0], city: city });
return { value: degree[0].degree, city: city };
}
}
).catch(err => console.log(err))
}
How can I solve this problem?
The issue is that you don't return anything in controlPredictedWeather function
const controlPredictedWeather = city => {
// vvv added return here
return Predicted_Weather.findAll({ where: { CITYID: city.ID } }).then(
degree => {
if (degree.length === 0) {
return (getPredictedWeather(city)); // it goes another function
}
else {
console.log({ value: degree[0], city: city });
return { value: degree[0].degree, city: city };
}
}
).catch(err => console.log(err))
}
Having solved the issue, now you can address the elephant in the room
you have an async function with no await
You're using the Promise constructor anti-pattern - i.e. new Promise where it's never required
Since you have used async keyword, that suggests you want to use the somewhat easier to read async/await pattern of using Promises
So, why not use it fully
Like this
exports.getCity = async (req, res) => {
const control = await controlCity(req.params.city);
res.render('index.ejs', control);
};
const controlCity = async (cityStub) => {
try {
const city = await City.findAll({ where: { SEHIRSTUB: cityStub } });
if (city.length === 0) { // when it comes here it works
return { value: "test" };
}
// no need for else since returning above
return controlPredictedWeather(city[0]);
} catch(err) {
console.log(err);
}
};
const controlPredictedWeather = async (city) => {
try {
const degree = await Predicted_Weather.findAll({ where: { CITYID: city.ID } });
if (degree.length === 0) {
return (getPredictedWeather(city)); // it goes another function
}
// no need for else since returning above
console.log({ value: degree[0], city: city });
return { value: degree[0].degree, city: city };
} catch(err) {
console.log(err)
}
};
Of course, there is one elephant left in the room, and that is that control in
res.render('index.ejs', control);
WILL be undefined if either of .findAlls throw an error - this will also be the case in your original code, just thought I'd mention that

module.exports returning undefined

I`m trying to make a translation function with IBM Watson API in "services/ibmWatson/index.js". I receive the response correctly, but when I return the response to the "IncidentController.js" it receives as undefined.
const LanguageTranslatorV3 = require('ibm-watson/language-translator/v3');
const { IamAuthenticator } = require('ibm-watson/auth');
module.exports = {
async translate(phrase, language) {
const languageTranslator = new LanguageTranslatorV3({
authenticator: new IamAuthenticator({ apikey: '<my_API_key>' }),
url: 'https://gateway.watsonplatform.net/language-translator/api/',
version: '2020-03-28',
});
await languageTranslator.translate(
{
text: phrase,
source: 'pt',
target: language
})
.then(response => {
if(response.status=200){
console.log(response.result.translations);
return(response.result.translations);
}
return (["error"]);
})
.catch(err => {
console.log('error: ', err);
return (["error"]);
});
}
}
In the above code the console.log(response.result.translations) returns correctly:
[ { translation: 'Dog run over.' },
{ translation: 'Castration' },
{ translation: 'Ticks' },
{ translation: 'Tuberculosis' } ]
In the in IncidentController.js:
const Watson = require('../../src/services/ibmWatson');
const db_connection = require('../database/connection');
module.exports = {
async index(request, response) {
const incidents = await db_connection('incidents').join('ongs', 'ongs.id', '=', 'incidents.ong_id')
.select([
'incidents.*',
'ongs.name',
'ongs.uf']
);
const titles = [];
incidents.forEach((incident) => { titles.push(incident.title) });
const translated_titles = await Watson.translate(titles, "en");
console.log(translated_titles);
return response.json(incidents);
}
}
In the above code the console.log(response.result.translations) returns undefined.
What is wrong with it?
You are returning response.result.translations to the response callback from then().
Since that callback cannot be accessed by your IncidentController, it returns undefined.
This is one way to solve this problem:
// services/ibmWatson/index.js
translate(phrase, language) {
return new Promise((resolve, reject) => {
try {
const response = await languageTranslator.translate({ /* options */ });
resolve(response); // Return the translations
} catch(error) {
reject(error); // If there's an error, return the error
}
});
}
// IncidentController.js
async index() {
// ...
const translatedTitles = await Watson.translate(titles, "en");
console.log(translatedTitles); // Should be working now
}
I hope I could help you or at least lead you in the right direction.

How to get the result of async / await function?

I would like to return an object from the the async / await function A to pass it to another function.
Currently what I get as a result is Promise{ <pending> }' or undefined.
function A:
const parseRss = data => data.forEach(rssLink => {
const getFeed = async () => {
try {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: []
}
}
feed.items.forEach(item => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`)
});
return emailContent;
} catch (e) {
console.error(e);
}
};
return (async () => {
return await getFeed();
})();
});
Function B:
try {
const data = await getDataWithRss();
const emailData = await parseRss([{rss:'http://reddit.com/.rss'}]); // emailData is undefined
return formatEmail(emailData);
} catch (error) {
console.log(error);
}
How do I return emailContent from function A to use it in function B?
Thanks!
Since you made getFeed as async, no need another async. You are looping through, so return an array of promises. Once the you call use Promise.all to resolve. Since there could be multiple urls to fetch.
const parseRss = (data) =>
data.map((rssLink) => {
const getFeed = async () => {
try {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: [],
},
};
feed.items.forEach((item) => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`);
});
return emailContent;
} catch (e) {
console.error(e);
}
};
return getFeed();
});
try {
const data = await getDataWithRss();
const emailData = await Promise.all(parseRss([{rss:'http://reddit.com/.rss'}])); // emailData is undefined
return formatEmail(emailData);
} catch (error) {
console.log(error);
}
await will not work inside a forEach loop. Use a for...in loop instead.
actually, getFeed() is not necessary inside inner scope, you can use async in map callback:
const parseRss = data => data.map(async rssLink => {
const feed = await rssParser.parseURL(rssLink.rss);
const emailContent = {
emailBody: {
title: feed.title,
content: []
}
};
feed.items.forEach(item => {
feedObj.emailBody.content.push(`${item.title} : ${item.link}`)
});
return emailContent;
});

Node-Fetch Mapping Error - Cannot read property 'map' of undefined"

Getting an error with the "map" part when I try and run it Cannot read property 'map' of undefined"
The customers const is declared above so not sure. Where is the undefined is coming from? Does the map need declaring?
const AWS = require('aws-sdk'),
ses = new AWS.SES(),
fetch = require('node-fetch');
exports.handler = async (event) => {
console.log(event.customer_id);
const customers = await getCustomers();
customers.map(async customer => await sendEmailToCustomer(customer));
const customersEmailsPromises = customers.map(async customer => await sendEmailToCustomer(customer));
}
async function getCustomers() {
try {
const resp = await fetch('https://3objects.netlify.com/3objects.json');
const json = await resp.json();
return json;
}
catch(e) {
throw e;
}
}
const sendEmailToCustomer = (customer) => new Promise((resolve, reject) => {
ses.sendEmail({
Destination:
{ ToAddresses: [customer.email] },
Message:
{
Body: { Text: { Data: `Your contact option is ${customer.customer_id}` } },
Subject: { Data: "Your Contact Preference" }
},
Source: "sales#example.com"
}, (error, result => {
if (error) return reject(error);
resolve(result);
console.log(result);
})
);
})
getCustomers doesn't return anything which means that customers is set to undefined.
Try this:
async function getCustomers() {
try {
const resp = await fetch('https://3objects.netlify.com/3objects.json');
const json = await resp.json();
return json;
}
catch(e) {
throw e;
}
}
You also have to return something from the function that you pass as a parameter to .map
customers.map(async customer => {
return await sendEmailToCustomer(customer);
});
or just:
customers.map(async customer => await sendEmailToCustomer(customer));
And since .map returns a new array (does not mutate the original array), you'll have to store the return value:
const customersEmailsPromises = customers.map(async customer => await sendEmailToCustomer(customer));

Categories

Resources