await not working on res.app.render in Express.js - node.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)
...
})

Related

how to send response only after mv library on nodejs completes. Wrapping in promise doesn't work

I'm trying to setup an endpoint that takes a file through a multipart post request, and saves it into a specific directory using formidable and https://github.com/andrewrk/node-mv. And then upon completion of saving all of the files, I want to respond with a list of all of the files in that directory for rendering. the thing is the response seems to be sent before the directory listing is updated. I tried wrapping the mv operations into a promise and then responding in a then block to no avail. Any help would be much appreciated!
app.post("/api/v1/vendor/:id/menu", (req, res, next) => {
const id = req.params.id;
const form = formidable({ multiples: true, keepExtensions: true });
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
if (!Array.isArray(files.image)) {
files = [files.image];
}
let filelist;
const proms = files.map((file) => {
const dst = `pics/${id}/${file.name}`;
new Promise((resolve, reject) => {
mv(file.path, dst, { mkdirp: true }, (err) => {
if (err) {
console.error("error: ", err.status);
reject(err);
}
console.log("done moving");
resolve();
});
});
});
Promise.all(proms).then(() => {
console.log('now reading dir...');
filelist = fs.readdirSync("pics/" + id);
res.send(filelist);
});
});
});
I think we're missing the return keywork before new Promise. You can check the proms variable if it contains the list of promises or not.
const proms = files.map((file) => {
const dst = `pics/${id}/${file.name}`;
new Promise((resolve, reject) => {
mv(file.path, dst, { mkdirp: true }, (err) => {
if (err) {
console.error("error: ", err.status);
reject(err);
}
console.log("done moving");
resolve();
});
});
});
For me, it should be :
const proms = files.map((file) => {
const dst = `pics/${id}/${file.name}`;
return new Promise((resolve, reject) => {
mv(file.path, dst, { mkdirp: true }, (err) => {
if (err) {
console.error("error: ", err.status);
reject(err);
}
console.log("done moving");
resolve();
});
});
});

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);
});
});
}

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.

Cloudinary async wait upload for multiple images at once then save to array in node js

Hi I am look for a solution for API server side upload using Node Js asyc wait functionality for uploading multiple images then catch into an array. I follow this link to implement my code. In console it shows the array that I expect, but it does not give me any responses.
Here is what I tried so far,
exports.upload_image = async(req, res) =>{
//let filePaths = req.files.path;
let multipleUpload = new Promise(async (resolve, reject) => {
let upload_len = req.files.length;
let upload_res = new Array();
for(let i = 0; i < upload_len; i++)
{
let filePath = req.files[i].path;
await cloudinary.v2.uploader.upload(filePath, { use_filename: true, unique_filename: false }, function (error, result) {
if(upload_res.length === upload_len)
{
/* resolve promise after upload is complete */
resolve(upload_res)
}
else if(result)
{
/*push public_ids in an array */
upload_res.push(result.public_id);
} else if(error) {
console.log(error)
reject(error)
}
})
}
})
.then((result) => result)
.catch((error) => error)
let upload = await multipleUpload;
res.json({'response':upload})
}
Any suggestion would be appreciated. Thanks.
I would take a different approach here. You are mixing up promises and async/await in a way that is pretty hard to read and debug.
I would do this instead:
Map your files to an array of promises and then call Promise.all():
exports.upload_image = async(req, res) =>{
// res_promises will be an array of promises
let res_promises = req.files.map(file => new Promise((resolve, reject) => {
cloudinary.v2.uploader.upload(file.path, { use_filename: true, unique_filename: false }, function (error, result) {
if(error) reject(error)
else resolve(result.public_id)
})
})
)
// Promise.all will fire when all promises are resolved
Promise.all(res_promises)
.then(result => res.json({'response':upload}))
.catch((error) => {/* handle error */ })
}
Here's a snippet with a fake upload function and a list of 'files':
function upload(file, fn) {
// fake upload file, just return file as id
setTimeout(() => fn(null, {
public_id: file
}), Math.floor(Math.random() * 500))
}
// fake req.files
let files = [1, 2, 3, 4, 5, 6]
let upload_image = () => {
let upload_res = files.map(file => new Promise((resolve, reject) => {
upload(file, (error, result) => {
if (error) reject(error)
else resolve(result.public_id);
})
}))
Promise.all(upload_res)
.then(result => console.log({
'response': result
}))
.catch(error => error)
}
upload_image()

Aysnc for each callback issue

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")
})

Resources