使用html-pdf-node+Promise.all批量生成PDF遇Target closed协议错误求助
问题描述
使用html-pdf-node包批量生成PDF时,通过Array.map()创建异步Promise数组并使用Promise.all()等待全部完成,但生成部分文件后出现Protocol error (Page.printToPDF): Target closed错误。
代码示例
async function generatePDFs(result) { const filename = []; const generatePdfPromiseArray = result.map(async (item) => { const file_name = getRandomFileName(); const html = "abc"; const margin = { top: "210px", right: "100px", bottom: "150px", left: "80px", }; const options = { format: "A4", path: `some/path/${file_name}.pdf`, margin: margin, }; try { const pdfBuffer = await html_to_pdf.generatePdf({ content: html }, options); await fs.writeFileSync(options.path, pdfBuffer); filename.push(file_name); console.log("PDF saved successfully!"); } catch (error) { console.error("Error generating PDF:", error); } }); // Wait for all the promises to resolve await Promise.all(generatePdfPromiseArray); return filename; }
解决思路与方案
这个错误一般是因为html-pdf-node依赖的Puppeteer实例被过早关闭,或是并发任务过多导致浏览器资源耗尽,可从以下方向修复:
1. 限制并发数,避免资源过载
Promise.all()会一次性触发所有异步任务,瞬间创建大量Puppeteer页面/浏览器实例,超出资源承载上限。可以改用串行或限制并发的方式:
串行处理(适合小批量场景)
async function generatePDFs(result) { const filename = []; // 逐个处理任务,避免并发过载 for (const item of result) { const file_name = getRandomFileName(); const html = "abc"; const margin = { top: "210px", right: "100px", bottom: "150px", left: "80px", }; const options = { format: "A4", path: `some/path/${file_name}.pdf`, margin: margin, }; try { await html_to_pdf.generatePdf({ content: html }, options); filename.push(file_name); console.log("PDF saved successfully!"); } catch (error) { console.error("Error generating PDF:", error); } } return filename; }
限制并发数(适合大批量场景)
借助p-limit包控制同时运行的任务数量:
const pLimit = require('p-limit'); const limit = pLimit(3); // 限制同时运行3个PDF生成任务 async function generatePDFs(result) { const filename = []; const generatePdfPromiseArray = result.map(item => limit(async () => { const file_name = getRandomFileName(); const html = "abc"; const margin = { top: "210px", right: "100px", bottom: "150px", left: "80px", }; const options = { format: "A4", path: `some/path/${file_name}.pdf`, margin: margin, }; try { await html_to_pdf.generatePdf({ content: html }, options); filename.push(file_name); console.log("PDF saved successfully!"); } catch (error) { console.error("Error generating PDF:", error); } })); await Promise.all(generatePdfPromiseArray); return filename; }
2. 复用Puppeteer浏览器实例
html-pdf-node默认每次调用generatePdf都会创建新浏览器实例,批量处理时会浪费资源。可以手动创建一个浏览器实例复用:
const puppeteer = require('puppeteer'); async function generatePDFs(result) { const filename = []; // 提前创建浏览器实例 const browser = await puppeteer.launch(); const generatePdfPromiseArray = result.map(async (item) => { const file_name = getRandomFileName(); const html = "abc"; const margin = { top: "210px", right: "100px", bottom: "150px", left: "80px", }; const options = { format: "A4", path: `some/path/${file_name}.pdf`, margin: margin, browser: browser, // 复用已创建的浏览器实例 }; try { await html_to_pdf.generatePdf({ content: html }, options); filename.push(file_name); console.log("PDF saved successfully!"); } catch (error) { console.error("Error generating PDF:", error); } }); await Promise.all(generatePdfPromiseArray); await browser.close(); // 所有任务完成后关闭浏览器 return filename; }
3. 移除冗余IO操作
原代码中options已指定path,html_to_pdf.generatePdf会自动将PDF写入该路径,无需再调用fs.writeFileSync重复写入,这会额外占用IO资源,直接移除即可:
// 移除这行冗余代码 // await fs.writeFileSync(options.path, pdfBuffer);
内容的提问来源于stack exchange,提问作者Rizwan Raza
相关产品推荐
相关产品推荐

