You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js如何编写非阻塞代码?PDF生成路由阻塞问题解决

解决PDF生成导致的路由阻塞问题

问题原因

Node.js基于单线程事件循环模型,当路由处理中存在同步IO操作或CPU密集型同步任务时,会占用事件循环,导致后续所有请求都被阻塞,必须等待当前任务完成才能处理。你的代码中存在两个核心问题:

  1. 使用fs.readFileSync同步读取文件,直接阻塞事件循环;
  2. 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生成任务,使用子进程隔离处理,主进程专注于响应请求:

  1. 新建独立的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 });
  }
});
  1. 修改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进程处理任务,路由立刻返回任务状态:

  1. 安装依赖:npm install bullmq
  2. 配置队列与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;
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 09:05:39