Node.js如何编写非阻塞代码?PDF生成路由阻塞问题解决
解决PDF生成导致的路由阻塞问题
问题原因
Node.js基于单线程事件循环模型,当路由处理中存在同步IO操作或CPU密集型同步任务时,会占用事件循环,导致后续所有请求都被阻塞,必须等待当前任务完成才能处理。你的代码中存在两个核心问题:
- 使用
fs.readFileSync同步读取文件,直接阻塞事件循环; - PDF生成本身是CPU密集型操作,在主进程执行时会占用事件循环资源。
具体解决方案
方案1:替换同步IO为异步IO
将所有同步文件读取操作改为异步,释放事件循环让其他请求可以被处理:
修改PDFCreate函数中的同步读取代码:
// 原同步代码 // const html = fs.readFileSync('app/data/file.html', 'utf-8'); // const bitmap = fs.readFileSync('public/images/lg.jpeg'); // 改为异步await版本 const html = await fs.readFile('app/data/file.html', 'utf-8'); const bitmap = await fs.readFile('public/images/lg.jpeg');
同时修复pdf.create的异步处理问题(原代码未等待生成完成就返回路径):
// 原代码 // pdf.create(document, options) // .then(res => { console.log(res); }).catch(error => { console.log(error); }); // 改为await版本 await pdf.create(document, options);
方案2:将PDF生成移至独立子进程
对于CPU密集型的PDF生成任务,使用子进程隔离处理,主进程专注于响应请求:
- 新建独立的PDF生成进程文件
pdf-generator.js:
const pdf = require("pdf-creator-node"); const fs = require('fs-extra'); process.on('message', async () => { try { const options = { format: "A3", orientation: "portrait", border: "10mm", header: { height: "0mm" }, footer: { height: "28mm", contents: { first: 'Cover page', 2: 'Second page', default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', last: 'Last Page' } } }; const html = await fs.readFile('app/data/file.html', 'utf-8'); const bitmap = await fs.readFile('public/images/lg.jpeg'); const logo = bitmap.toString('base64'); const filename = Math.random() + '_doc' + '.pdf'; const document = { html: html, data: { logo }, path: './docs/' + filename }; await pdf.create(document, options); const filepath = 'http://localhost:3000/public/images/' + filename; process.send({ success: true, filepath }); } catch (e) { process.send({ success: false, error: e.message }); } });
- 修改
PDFCreate函数,通过子进程调用生成逻辑:
import { fork } from 'child_process'; import path from 'path'; export default async function PDFCreate() { return new Promise((resolve, reject) => { const child = fork(path.join(__dirname, 'pdf-generator.js')); child.send({}); child.on('message', (msg) => { child.kill(); msg.success ? resolve(msg.filepath) : reject(new Error(msg.error)); }); child.on('error', reject); }); }
方案3:使用消息队列处理(高并发场景)
如果PDF生成请求量较大,用消息队列(如BullMQ)将任务异步化,后台worker进程处理任务,路由立刻返回任务状态:
- 安装依赖:
npm install bullmq - 配置队列与worker:
import { Queue, Worker } from 'bullmq'; // 创建PDF生成队列 const pdfQueue = new Queue('pdf-generation', { connection: { host: 'localhost', port: 6379 } // 依赖Redis服务 }); // 启动worker处理队列任务 new Worker('pdf-generation', async () => { const pdf = require("pdf-creator-node"); const fs = require('fs-extra'); const options = { /* 保持原配置 */ }; const html = await fs.readFile('app/data/file.html', 'utf-8'); const bitmap = await fs.readFile('public/images/lg.jpeg'); const logo = bitmap.toString('base64'); const filename = Math.random() + '_doc' + '.pdf'; const document = { html: html, data: { logo }, path: './docs/' + filename }; await pdf.create(document, options); return 'http://localhost:3000/public/images/' + filename; }); export default pdfQueue;
- 修改
PDFCreate函数加入队列:
import pdfQueue from './queue'; export default async function PDFCreate() { const job = await pdfQueue.add('generate-pdf', {}); // 返回任务ID,让前端后续轮询状态 return { jobId: job.id, status: 'pending' }; }
效果验证
修改后,访问/pdf路由时,主进程不会被阻塞,/home等其他路由可以正常响应请求。
内容的提问来源于stack exchange,提问作者reacter777
相关产品推荐
相关产品推荐

