I have the below function to print receipts:
const generateOnePDF = async (ticket, orderData) =>
new Promise(async (resolve, reject) => {
let finalString = '';
if (!ticket) {
const err = new Error('missing ticket data');
reject(err);
}
if (!orderData) {
const err = new Error('missing order data');
reject(err);
}
const { payload, sequence, title } = ticket;
if (!payload) {
const err = new Error('missing payload data');
reject(err);
}
if (!sequence) {
const err = new Error('missing ticket sequence');
reject(err);
}
if (!title) {
const err = new Error('missing ticket book title');
reject(err);
}
const doc = new PDFDocument();
PDFDocument.prototype.addSVG = function (svg, x, y, options) {
return SVGtoPDF(this, svg, x, y, options);
};
const stream = doc.pipe(new Base64Encode());
// logo
const logo = await fetchImage(
`${url}/static/media/logo_157.c15ac239.svg`
);
doc.addSVG(logo.toString(), 32, 40, {});
doc.moveUp();
doc.moveUp();
doc.text(`Order: O${orderData.orderId}`, { align: 'right' });
const rectXOffset = 25;
const rectPosition = 32;
doc
.rect(rectXOffset, rectPosition, doc.page.width - rectXOffset * 2, 32)
.stroke();
doc.moveDown();
doc.moveDown();
doc.moveDown();
doc.rect(rectXOffset, 80, doc.page.width - rectXOffset * 2, 680).stroke();
doc.text(orderData.title, { align: 'left' });
doc.moveDown();
doc.text(orderData.venue, { align: 'left' });
doc.text(orderData.address, { align: 'left' });
doc.text(`${orderData.city}, ${orderData.province} ${orderData.zip}`, {
align: 'left'
});
if (orderData.custom) {
doc.moveDown();
doc.text(orderData.customCopy, { align: 'left' });
}
doc.moveDown();
doc.text(`${orderData.date} at ${orderData.show}`, { align: 'left' });
doc.moveDown();
const image = await fetchImage(orderData['image']);
doc.image(image, {
fit: [100, 100],
align: 'right',
valign: 'top'
});
doc.moveDown();
doc.text(
`Order: O${orderData.orderId}. Placed by ${orderData.firstName} ${orderData.lastName} on ${orderData.created}`
);
// right column
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.moveUp();
doc.text(`Ticket: ${sequence} ${title}`, { align: 'right' });
const qrcode = new QRCode({
content: payload,
width: 200,
height: 200,
xmlDeclaration: false,
join: true
}).svg();
const options = { align: 'right' };
doc.addSVG(qrcode, 400, 150, options);
// finalize/close the PDF file
doc.end();
stream.on('data', (chunk) => {
finalString = chunk;
});
stream.on('end', () => {
// the stream is at its end, so push the resulting base64 string to the response
resolve(finalString);
});
stream.on('error', (err) => {
reject(err);
});
});
It is not the cleanest piece of code in the world, but it works for me, in the meantime.
I added a simple unit test for this code, below:
it('should throw an error if the ticket or order data are invalid', async () => {
await expect(generateOnePDF(null, {})).rejects.toThrowError();
await expect(generateOnePDF({}, null)).rejects.toThrowError();
});
The tests passes, but it writes "garbage" to the console. There are unhandled rejects in the code.
(node:53703) UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property 'payload' of '((cov_2gbfm4phuo(...).s[25] ) , ticket)' as it is null.
(Use `node --trace-warnings ...` to show where the warning was created)
is one such error and another is:
(node:53703) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'orderId' of null
What I don't understand why if (!ticket) throw()
and if (!orderData) throw
would not prevent the errors from occurring. Where is the "broken promise?"
I am doing reject
inside the promise handler function, so where I am not doing it?
CodePudding user response:
The problem is that your function still continues executing after reject
is called, and so errors occur.
Moreover, you should not create a new Promise
with an async
callback. That is an anti-pattern. Instead promisify the stream "end" event, and then return that promise.
I would also refrain from defining a function on that PDFDocument
prototype, and certainly not on every call of generateOnePDF
. You might as well just call that SVGtoPDF
function directly.
So something like this:
const streamPromise = stream =>
return new Promise((resolve, reject) => {
let finalString = '';
stream.on('data', chunk => finalString = chunk);
stream.on('end', () => resolve(finalString));
stream.on('error', reject);
});
const generateOnePDF = async (ticket, orderData) => {
if (!ticket ) throw new Error('missing ticket data');
if (!orderData) throw new Error('missing order data');
const { payload, sequence, title } = ticket;
if (!payload ) throw new Error('missing payload data');
if (!sequence ) throw new Error('missing ticket sequence');
if (!title ) throw new Error('missing ticket book title');
const doc = new PDFDocument();
const stream = doc.pipe(new Base64Encode());
// logo
const logo = await fetchImage(`${url}/static/media/logo_157.c15ac239.svg`);
SVGtoPDF(doc, logo.toString(), 32, 40, {});
// ... etc ...
// ...
doc.end();
return streamPromise(stream);
});
CodePudding user response:
This is caused by a combination of using an async
function for the executor argument to new Promise
and not return
ing early after the reject()
call. Without either of these, you wouldn't both reject()
the promise and also cause the failure to reject the promise returned by the async
function. Fixing both, you'd write
async function generateOnePDF(ticket, orderData) {
if (!ticket) throw new Error('missing ticket data');
if (!orderData) throw new Error('missing order data');
const { payload, sequence, title } = ticket;
if (!payload) throw new Error('missing payload data');
if (!sequence) throw new Error('missing ticket sequence');
if (!title) throw new Error('missing ticket book title');
const doc = new PDFDocument();
const logo = await fetchImage(`${url}/static/media/logo_157.c15ac239.svg`);
doc.addSVG(logo.toString(), 32, 40, {});
…
return new Promise((resolve, reject) => {
const stream = doc.pipe(new Base64Encode());
let finalString = '';
stream.on('data', chunk => {
finalString = chunk;
});
stream.on('end', () => {
resolve(finalString);
});
stream.on('error', reject);
doc.end();
});
});