Search code examples
javascriptnode.jsasync-awaitnode-streams

Working with Node.js streams without callbacks


To send a PDF file from a Node.js server to a client I use the following code:

const pdf = printer.createPdfKitDocument(docDefinition);

const chunks = [];

pdf.on("data", (chunk) => {
    chunks.push(chunk);
});

pdf.on("end", () => {
    const pdfBuffered = `data:application/pdf;base64, ${Buffer.concat(chunks).toString("base64")}`;
    res.setHeader("Content-Type", "application/pdf");
    res.setHeader("Content-Length", pdfBuffered.length);
    res.send(pdfBuffered);
});

pdf.end();

Everything is working correctly, the only issue is that the stream here is using callback-approach rather then async/await.

I've found a possible solution:

const { pipeline } = require("stream/promises");

async function run() {
    await pipeline(
        fs.createReadStream('archive.tar'),
        zlib.createGzip(),
        fs.createWriteStream('archive.tar.gz')
    );

    console.log('Pipeline succeeded.');
}

run().catch(console.error);

But I can't figure out how to adopt the initial code to the one with stream/promises.


Solution

  • You can manually wrap your PDF code in a promise like this and then use it as a function that returns a promise:

    function sendPDF(docDefinition) {
        return new Promise((resolve, reject) => {
            const pdf = printer.createPdfKitDocument(docDefinition);
    
            const chunks = [];
    
            pdf.on("data", (chunk) => {
                chunks.push(chunk);
            });
    
            pdf.on("end", () => {
                const pdfBuffered =
                    `data:application/pdf;base64, ${Buffer.concat(chunks).toString("base64")}`;
                resolve(pdfBuffered);
            });
    
            pdf.on("error", reject);
    
            pdf.end();
        });
    }
    
    sendPDF(docDefinition).then(pdfBuffer => {
        res.setHeader("Content-Type", "application/pdf");
        res.setHeader("Content-Length", pdfBuffer.length);
        res.send(pdfBuffer);
    }).catch(err => {
        console.log(err);
        res.sendStatus(500);
    });
    

    Because there are many data events, you can't promisify just the data portion. You will still have to listen for each data event and collect the data.