0

I am using node html-pdf to generate a pdf and nodemailer to automatically send an email containing the pdf every minute using node-cron as demonstrated in the following code:

cron.schedule('* * * * *', () => {
    
    // function to generate scan report using html-pdf
    report.getScanReport();

    // e-mail message options
    let mailOptions = {
        from: {{some_email}},
        to: {{some_email}},
        subject: 'Testing',
        text: 'See PDF attachment',
        attachments: [{
            filename:'test.pdf',
            path: 'C:/Users/test.pdf',
            contentType: 'application/pdf'
        }]
    };

    // Send e-mail
    transporter.sendMail(mailOptions, function(error, info) {
        if (error) {
            console.log(error);
        } else {
            console.log('Email sent: ' + info.response);
        }
    });
})

The issue is that generating the pdf takes ~10 seconds or so, so the program tries to send the email before the generated pdf file exists and I then get the error: Error: ENOENT: no such file or directory

For reference here is how the getScanReport() function works:

const getScanReport = () => {
      Promise.all([
        pool.query(query1),
        pool.query(query2)
    ]).then(function([results1, results,]) {
        var obj1 = results1.rows;
        var obj2 = results2.rows;

        ejs.renderFile('./views/pages/scanreport.ejs', {scanObj: scanObj, woObj: woObj}, (err, data) => {
        if (err) {
            console.log(err);
        } else {
            let options = {
            "height": "11.25in",
            "width": "8.5in",
            "header": {
                "height": "20mm"
            },
            "footer": {
                "height": "20mm",
            },
            };
            let filename = "test.pdf";
            pdf.create(data).toFile(filename, function (err, data) {
            let fileloc = 'C:/Users/' + filename;
            if (err) {
                console.log(err);
            } else {
                console.log("Successfully created pdf");
            }
            })
        }
        });
    });
} 

Is there a way make sure the pdf is generated before trying to send the email?

NicLovin
  • 317
  • 3
  • 20
  • 2
    The problem here is in `getScanReport()`. It needs communicate back when it has completed its work, either via a callback or by returning a promise that resolves when the work is done. Timers and retries are hacks and are not the right way to solve this problem. The right way is to fix the code that generates the PDF to it communicates back completion. If you include the code for that function (and any code that it calls), then we might be able to help you more specifically. – jfriend00 Sep 30 '21 at 16:08
  • 1
    Deleting my previous comments because I wholeheartedly agree with @jfriend00. The real solution here is to have `getScanReport` tell you when it's finished. I was answering the specific question instead of pointing to a proper solution. – ray Sep 30 '21 at 16:10

2 Answers2

2

Assuming you can modify your getScanReport function, use a Promise. Then in your email function, use async / await to wait for that promise.

(You can click the links to read more about how promises and async / await work if you don't use them very often.)

For example (edited since post now includes getScanReport inner workings):

report.getScanReport = () => new Promise( 

    //announcePDFReady() is a function we call to resolve our promise

    announcePDFReady => {

        Promise.
            all( [ /* ... */ ] ).
            then( function( [ results1, results2 ] ) {

                /* ... */

                renderFile( /* ... */ ( err, data ) => {

                    /* ... */

                    pdf.create( data ).toFile( filename, ( err, data ) => {
                        
                        let fileloc = 'C:/Users/' + filename;
                        if (err) {
                            console.log(err);
                        } else {
                            console.log("Successfully created pdf");
                            //announce ready right here!
                            announcePDFReady( data );
                        }

                    } )

                } )

            } )
            
    }

);


//...
//we write 'async' before the cron job function definition
async () => {
    //this is inside the cron job function

    const pdfIsReady = await report.getScanReport();
    //since report.getScanReport() returns a promise,
    //'await' here means our code will stop until the promise resolves.


    if( pdfIsReady === false ) {
        //it failed. Do something? Ignore it?
    } else {

        //pdf is ready. We can send the email
    
        //etc.

    }
}
Michael G
  • 458
  • 2
  • 9
  • Can I still do this even if I have a promise inside of the getScanReport function? I have edited my question so that you can view the contents of the getScanReport() function – NicLovin Sep 30 '21 at 17:27
  • 1
    @NicLovin Yes! Very convenient. Just wrap your entire getScanReport() in a promise, and resolve that promise at the place where you have `console.log("Successfully created pdf")`. Updated answer. – Michael G Sep 30 '21 at 17:36
  • okay perfect, the only problem now is I am getting the error ```SyntaxError: await is only valid in async functions and the top level bodies of modules``` . I tried googling that error but I am unsure of how to solve it – NicLovin Sep 30 '21 at 17:51
  • 1
    @NicLovin you must include the keyword 'async' in front of your cron job function, like: `cron.schedule('* * * * *', async () => {`. – Michael G Sep 30 '21 at 21:37
  • Perfect that works thanks I appreciate it – NicLovin Oct 01 '21 at 12:17
0

Assuming you're using this NPM package: https://www.npmjs.com/package/html-pdf-node, you can use the promises that it provides in order to do something after the creation of the PDF file.

You can see it creates a PDF, and then executes additional code. I've edited it a bit to make it easier to understand.

html_to_pdf.generatePdf(file, options).then(() => {
  // this code will execute after the PDF has been generated.
});

If you are using a different package, can you please link it. Thank you.

Zeval
  • 1