Aysnc for each callback issue - node.js

I am using async for each to achieve some task the problem i am facing is that the final call back never executes
Scenario : i have list of contacts and want to send message to all contacts in parallel and when message is send want to store the response in array and than want to perform some action on final call back
sms.js
function SmsService() {}
SmsService.prototype.sendSms = function(value, callback) {
client.messages
.create({
body: value.body,
from: value.from,
to: value.to
})
.then(message => {
console.log('meesage going', message.sid);
callback(null,message.sid)
})
.catch(e => {
callback(null,'not send')
})
}
module.exports = SmsService;
sender.js
var SmsService = require(path.resolve(__dirname, './sms'));
var smsService = new SmsService();
var data = [{body:'1232324',from:'+12323123',to:'+12312323'},
{body:'112123234',from:'+123123123',to:'+123213123'}, {body:'12sadasdasd34',from:'+112123123',to:'+1223213123'}]
async.forEachOf(data, function (value, i, cb) {
console.log('started',i)
smsService.sendSms(value, function(error, result) {
console.log('sending',i,value.to)//only get result for first item
results.push(result)
cb()
})
}, function (err) {
if (err) console.error(err.message);
console.log('all done')//never executes
console.log(results);//never executes
});
If I move the async part to SMS service it works fine but I want to keep separate the SMS service

You can try something like this.
sms.js
function SmsService() {}
SmsService.prototype.sendSms = function(value, callback) {
return new Promise((resolve, reject) => {
// Do async job
client.messages
.create({
body: value.body,
from: value.from,
to: value.to
})
.then(message => {
console.log('meesage going', message.sid);
resolve(callback(null,message.sid))
})
.catch(e => {
reject(callback(null,'not send'))
})
})
}
module.exports = SmsService;
sender.js
var SmsService = require(path.resolve(__dirname, './sms'));
var smsService = new SmsService();
var data = [{body:'1232324',from:'+12323123',to:'+12312323'},
{body:'112123234',from:'+123123123',to:'+123213123'},
{body:'12sadasdasd34',from:'+112123123',to:'+1223213123'}];
var promises = [];
data.forEach(async function (obj, index) {
console.log('started',index)
promises.push(await smsService.sendSms(obj, function(error, result) {
console.log('sending',i,value.to)//only get result for first item
results.push(result)
}));
});
Promise.all(promises).then(function () {
console.log("Messages sent")
}).catch(function (err) {
console.log("Messages not sent")
})

Related

await not working on res.app.render in Express.js

I want to generate multiple pdf files and attach to the email. But await seems not working on res.app.render.
route.get('/:id/receipts', async function (req, res) {
...
let attachments = [];
for await(let item of items){
res.view.item = item;
console.log(1)
await res.app.render('pdfs/receipt', res.view, async function(err, html){
console.log(2)
if (err) return res.end(err.stack)
return await pdf.create(html).toBuffer(async function(err, buffer){
console.log(3)
attachments.push({
content: buffer,
filename: 'receipt.pdf',
})
});
});
}
console.log(4)
...
})
Expect Result:
1
2
3
4
Actually Result:
1
4
2
3
I think res.app.render is not returning a promise that's why you are facing this issue. You have to make a custom promise. I hope following code will help you.
oute.get('/:id/receipts', async function (req, res) {
...
let attachments = [];
for await(let item of items){
res.view.item = item;
console.log(1)
const customPromise = new Promise((resolve, reject) => {
res.app.render('pdfs/receipt', res.view, async function(err, html){
console.log(2)
if (err) { res.end(err.stack);reject()}
else {
await pdf.create(html).toBuffer(async function(err, buffer){
console.log(3)
attachments.push({
content: buffer,
filename: 'receipt.pdf',
})
});
resolve();
}
});
})
}
console.log(4)
...
})

(nodejs gmail-api) Store messages in array?

I have a function that prints in the console all the unread messages. I'd like to store these messages in an array that I could use again later in the code. Is that possible ?
async function listMessages(auth, query) {
return new Promise((resolve, reject) => {
const gmail = google.gmail({version: 'v1', auth});
gmail.users.messages.list(
{
userId: 'me',
q: query,
}, (err, res) => {
if (err) {
reject(err);
return;
}
if (!res.data.messages) {
resolve([]);
return;
}
console.log(res.data.messages);
resolve(res.data.messages);
}
);
})
;}```
myNewMessage as your message:
var myArray = [];
myArray.push(myNewMessage)

My node js program is giving me a "TypeError: Cannot read property 'then' of undefined", when reading a json file

I'm trying to read frpm a json file folder withing my program and i want to use a GET list endpoint to read through browser or postman, but i'm getting the above TypeError. Here is my code:
model.js:
const fs = require('fs');
function loadTeams() {
return new Promise((resolve, reject) => {
fs.readFile('./json/prov-nodes.json', (err, data) => {
if (err) reject(err);
const teams = JSON.parse(data);
console.log(teams);
resolve(teams);
});
});
}
app.use(bodyParser.json());
app.get('/list', (req, res) => {
let teams = [];
loadTeams()
.then(function(data){
teams = JSON.stringify(data);
console.log(teams);
**res.send(teams);** //intended to send to browser/postman response
console.log('try...part ..read call');
})
.catch(error => console.log(error))
res.send("My root page");
console.log(teams);
});
The loadTeams function does not return a promise, and therefore you cannot call .then().
You can wrap the function in a promise like this:
function loadTeams() {
return new Promise(function(resolve, reject) {
fs.readFile('./json/prov-nodes.json', (err, data) => {
if (err) reject(err);
try {
const teams = JSON.parse(data);
return resolve(teams);
} catch(e) {
reject(e);
}
});
});
}
In order to use loadTeams as an async function you should turn it into a function that returns Promise with a callback results:
function loadTeams() {
return new Promise((resolve, reject) => {
fs.readFile('./json/prov-nodes.json', (err, data) => {
if (err) reject(err);
const teams = JSON.parse(data);
console.log(teams);
resolve(teams);
});
});
}

file write issue in Async/Await

Here I am trying to retrieve objects and push them into the array. For some reason there is only one record being pushed into the file when it should contain more objects. Can you help me out with this or let me know where I am going wrong? Here is my code:
exports.createjson = (req, res, next) => {
try {
var myPromise = () => {
// ...
};
var callMyPromise = async () => {
const responsearray = [];
var result = await myPromise();
return new Promise((resolve, reject) => {
result.forEach(element => {
NewsModel.findOne({ _id: element.newsId }).exec(
async (err, result) => {
if (err) {
throw err;
}
reportsModel
.findOne({
$and: [
{ userId: req.query.userId },
{ newsId: element.newsId }
]
})
.exec((err, newsResult) => {
if (err) {
throw err;
}
// console.log(newsResult);
var response = {
newsId: element.newsId,
title: result.title,
collection: result.group,
belivibalityIndex: element.belivibalityIndex,
priorknowledge: element.priorknowledge,
readingTime: element.readingTime,
userId: element.userId,
comment: element.comment,
report: newsResult !== null ? newsResult.feedback : null
};
// #all object pushed and displayed in console
responsearray.push(response);
console.log(response);
console.log(responsearray.length);
// let data = JSON.stringify(responsearray);
// #here is the issue // fs.writeFileSync("abc.json", data, null, null, flag = 'a');
return responsearray;
});
}
);
});
});
};
callMyPromise().then(function(responsearray) {
res.json(responsearray);
});
} catch (error) {
next(error);
}
};
You're not quite using Promises properly. For example, you create a Promise object but never call the resolve/reject functions. In the forEach loop you are calling functions that use callbacks and when that work is done you can resolve the promise you're wrapping it in.
Also you're calling res.json and writing the file (though it's commented out) while you're in the forEach loop. That means res.json will get called multiple times, which is not allowed. You can only have one response from an http request.
I restructured the code so that it collects each promise in an array of Promises then waits for all of them to resolve. Only after all of the work is done, we can write the file and call res.json to complete the http request.
exports.createjson = async (req, res, next) => {
const responsearray = [];
var elements = await myPromise();
var promises = []; // collect a bunch of promises to wait on
elements.forEach(element => {
// one promise per element that resolves when response is on the array
var promise = new Promise(function(resolve, reject) {
NewsModel.findOne({ _id: element.newsId }).exec((err, result) => {
if (err) { return reject(err); }
reportsModel
.findOne({
$and: [{ userId: req.query.userId }, { newsId: element.newsId }]
})
.exec((err, newsResult) => {
if (err) { return reject(err); }
var response = { /* response body */ };
responsearray.push(response);
console.log(response);
console.log(responsearray.length);
// complete the promise now that the response is on the array
return resolve();
});
});
});
// collect each promise in an array so we can wait for them all
promises.push(promise);
});
// wait for all the work to complete
await Promise.all(promises).catch(err => next(err));
// write the responsearray to a file as json
let data = JSON.stringify(responsearray);
fs.writeFileSync("abc.json", data);
return res.json(responsearray);
};
I also removed the try/catch block since the Promise allows you to use .catch in a cleaner way. It simplifies the nesting which makes it easier to read.
The key takeaway here is the general structure:
// get your array to work with
var array = await someFunction()
var manyPromises = []
var manyResults = []
// for each thing in the array create a promise
array.forEach( thing => {
manyPromises.push( new Promise((resolve,reject) => {
doSomething(thing, (err, result) => {
if (err) return reject(err);
// store the results in the array and resolve the promise
manyResults.push(result)
return resolve();
});
});
});
// wait for all promises in manyPromises to complete
await Promise.all(manyPromises).catch(err => return next(err));
// now the many promises are done and manyResponses are ready
saveResponsesToFile(JSON.stringify(manyResponses))
return res.json(manyReponses)

node csv files upload synchronous import followed by mongoose find

I am not able to get results from a mongoose find query after successfully uploading csv files. The data from the csvs is being saved in my collection OK. But I am getting no results from a following find query. What causes promises to not conform to the sequence? I feel I may be close to understanding promises as they are handled in the event loop but this case escapes me?
let uploadfiles = function (req,res) {
return new Promise( function (resolve, reject) {
upload(req, res, (err) => {
promises = [];
if (!err) {
if (Object.keys(req.files).length>0) {
simpleLoop(req.files, function (value, key, lovey) {
promises.push(insertStreamData(req.files[key]["originalname"]));
});
console.log('hither');
}
}
return Promise.all(promises);
});
resolve('something');
});
};
// insert uploaded csv data from files into db
function insertStreamData(filename){
var originalFileName = filename;
var thefilename = originalFileName.split('.');
var csvfile = "./public/files/"+originalFileName;
var stream = fs.createReadStream(csvfile, { headers: true});
var csvStream = csv().on("data", function(data){
if(data[0] != 'Product #' && data[7] != 0){
var item = new Product({
'Invoice':thefilename[0],
'Product #': data[0],
'SKU': data[1],
'UpcCode': data[2],
'Description': data[3],
'MfgNo': data[4],
'Vendor': data[5],
'Order Qty': data[6],
'Ship Qty': data[7],
'Min Sell': data[8],
'Retail': data[9],
'Cost': data[10],
'Ext Cost': data[11],
'Box': data[12]
});
item.save(function(error){ if(error){ throw error; } });
}
}).on("end", function(){
console.log('the end');
});
stream.pipe(csvStream);
}
let getincomingproducts = function(){
return new Promise(function (resolve, reject) {
resolve('done');
});
};
router.post('/uploaddata', function(req, res) {
uploadfiles(req,res).then(function(result){
return getincomingproducts();
}).then(function(result){
console.log(result);
res.redirect('/showdata');
}).catch(err => console.log("Caught " + err));
});
Output 'done' is logged to the console prior to the first promise. I want to replace done with mongoose find results. But a simple console log demonstrates the unsynchronous result.
done
hither
the end
the end
the end
the end
Please don't mix Promise constructors and such with async and await. This could be a lot easier if you just use await and async functions. And only wrap the callback taking functions once.
A clean version wouldn't have any business logic in the Promise constructors and would used await rather than then in an async function.
I found the second point in the program where it doesn't wait for the completion of an action that is that the insertStreamData does not return a promise that is fulfilled when it is done which is what is causing the out of order issue. I will update my answer.
I see a issue in upload files where it has:
return Promise.all(promises);
});
resolve('something');
which should be
return Promise.all(promises).then(function () {
resolve('something');
});
This change should cause the output to be hither, the end * 4, done. If you want the insertStreamData to occur in serial rather than parallel, that requires a different change.
My version of the script:
function upload_wrapper(req, res) {
return new Promise(function(resolve, reject) {
upload(req, res, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
})
}
let uploadfiles = async function(req, res) {
await upload_wrapper(req, res);
let promises = [];
if (Object.keys(req.files).length > 0) {
simpleLoop(req.files, function(value, key, lovey) {
promises.push(insertStreamData(req.files[key]["originalname"]));
});
console.log('hither');
}
await Promise.all(promises);
return 'something';
};
// insert uploaded csv data from files into db
function insertStreamData(filename) {
return new Promise(function(resolve, reject) {
var originalFileName = filename;
var thefilename = originalFileName.split('.');
var csvfile = "./public/files/" + originalFileName;
var stream = fs.createReadStream(csvfile, {
headers: true
});
var csvStream = csv();
csvStream.on("data", function(data) {
if (data[0] != 'Product #' && data[7] != 0) {
var item = new Product({
'Invoice': thefilename[0],
'Product #': data[0],
'SKU': data[1],
'UpcCode': data[2],
'Description': data[3],
'MfgNo': data[4],
'Vendor': data[5],
'Order Qty': data[6],
'Ship Qty': data[7],
'Min Sell': data[8],
'Retail': data[9],
'Cost': data[10],
'Ext Cost': data[11],
'Box': data[12]
});
item.save(function(error) {
if (error) {
//throw error;
csvStream.pause(); // here the stream should be destroyed if this stops at the first error
reject(error);
}
});
}
}).on("end", function() {
console.log('the end');
resolve('the end');
});
stream.pipe(csvStream);
});
}
let getincomingproducts = function() {
return Promise.resolve('done');
};
router.post('/uploaddata', async function(req, res) {
try {
let result = await uploadfiles(req, res);
let result = await getincomingproducts();
console.log(result);
res.redirect('/showdata');
} catch (err) {
console.log("Caught " + err)
}
});
Untested. It still could be improved.

Resources