Search code examples
async-awaitpdf-generationsendgrid

Async await not working for function - create pdf document and then email it


const pdf = require('pdfkit')
const QR = require('qrcode')
    
const emailTickets =  async (userEvent, tickets) => {   
    await createQRCodes(tickets)
    await createTicketPDF(tickets, userEvent)
    await sendGridEmail(emailDetails, true)
}   

The problem seems to be that async/await isn't working properly for the createTicketPDF function.

When I run the above emailTickets function a blank pdf document is emailed. However, when I run it with the below setTimeOut, the pdf contains all the detail that I want.

const emailTickets =  async (userEvent, tickets) => {   
    await createQRCodes(tickets)
    await createTicketPDF(tickets, userEvent)
    setTimeout(async() => {await sendGridEmail(emailDetails, true)}, 5000);
}   

I would prefer a solution where the code waited for the createTicketPDF function to finish before calling the sendGridEmail function.

Below is the code for the createTicketPDF function:

const createTicketPDF = async (tickets, userEvent) => {
    const doc = new pdf 
    doc.pipe(fs.createWriteStream(`./tickets/${tickets[0]._id}.pdf`))
    await tickets.map(async(ticket, index)=> {
        return(
            await doc.fontSize(30),
            await doc.text(userEvent.title),
            await doc.fontSize(10),
            await doc.moveDown(),
            await doc.text(`Venue: ${userEvent.venue}, ${userEvent.address1} `),
            await doc.moveDown(),
            await doc.fontSize(20),
            await doc.text(`Ticket ${index+1} of ${tickets.length}: ${ticket.ticketType}`), 
            await doc.image(`./qrCodes/${ticket._id}.png`, {
                align: 'center',
                valign: 'center'
            }),
            await doc.addPage()
        )
    })
    doc.end()   
}

I had a forEach loop in this function but replaced it with a map on learning that forEach loops don't work well with async await.

I have also tried using (const ticket of tickets){} but this didn't work either

VS code is suggesting that none of the awaits within the map do anything

For completeness, below is the createQRCodes. This function is working perfectly.

const createQRCodes = async (tickets) => {
    let CreateQRCodes = tickets.map(ticket => {     
        return(
        QR.toFile(`./qrCodes/${ticket._id}.png`, String([ticket._id, ticket.randomNumber, ticket.creationTime.getTime(), ticket.userEvent]))
        )})
    await Promise.all(CreateQRCodes)
}

Any ideas where I am going wrong


Solution

  • Twilio SendGrid developer evangelist here.

    As far as I can tell from the documentation, PDFKit is a synchronous library. The only thing that is asynchronous is writing the PDF to disk. So your code should not need to await anything within the createTicketPDF function. You do need to listen for the stream to finish though, so you could return a promise that resolves when the stream is finished, like this:

    const createTicketPDF = (tickets, userEvent) => {
        const doc = new pdf 
        const stream = doc.pipe(fs.createWriteStream(`./tickets/${tickets[0]._id}.pdf`))
        tickets.map((ticket, index)=> {
            return(
                doc.fontSize(30),
                doc.text(userEvent.title),
                doc.fontSize(10),
                doc.moveDown(),
                doc.text(`Venue: ${userEvent.venue}, ${userEvent.address1} `),
                doc.moveDown(),
                doc.fontSize(20),
                doc.text(`Ticket ${index+1} of ${tickets.length}: ${ticket.ticketType}`), 
                doc.image(`./qrCodes/${ticket._id}.png`, {
                    align: 'center',
                    valign: 'center'
                }),
                doc.addPage()
            )
        })
        doc.end()
        const promise = new Promise((resolve) => {
          stream.on("finish", () => {
            resolve();
          })
        });
        return promise;
    }
    
    const emailTickets =  async (userEvent, tickets) => {   
        await createQRCodes(tickets)
        await createTicketPDF(tickets, userEvent)
        await sendGridEmail(emailDetails, true)
    }