JavaScript:解决循环中Promise未完成致PDF剪辑顺序错乱问题
问题:PDF片段生成新PDF时顺序错乱
我正在开发一款Web应用,支持用户上传PDF文件,通过横向和纵向选择器截取PDF片段;用户可重新排列所选片段的顺序,应用将按照该顺序生成新的PDF文件。
目前遇到的问题是:应用生成的新PDF中,片段的顺序频繁出现错乱。
我推测问题出在代码逻辑上:循环迭代时,读取页面并粘贴片段的Promise尚未完成,程序就进入了下一次循环。我尝试将for循环放入async函数中,并在Promise前使用await,但未能解决问题。
请问我该如何修复这个问题?
原代码
var currentWriteHeight = 0; //used to determine how much is on a page in the new pdf before a new one must be created // open the pdf file to be clipped const fileName = '../static/upload/' + fileData['fileName']; pdfjsLib.getDocument(fileName).then((pdfRead) => { // create new pdf file var hSize = fileData['maxPageWidth']; var vSize = fileData['maxPageHeight']; const pdfWrite = new jsPDF({unit: 'px',format: [hSize, vSize]}); // this is the array that states what order the clips are in in the new file const positionArray = JSON.parse(fileData['positionArray']); for (const x in positionArray) { //get data for current clip const currentClip = clipData[positionArray[x]]; const currentPage = currentClip['pageNumber']; // put current page in a canvas pdfRead.getPage(currentPage).then((page) => { const canvas1 = document.createElement('canvas'); const ctx = canvas1.getContext("2d", { willReadFrequently: true }); const viewport = page.getViewport(1); canvas1.width = viewport.width; canvas1.height = viewport.height; page.render({ canvasContext: ctx, viewport: viewport }).then((rendered) => { // crop page down to dimensions specified in clip data const minY = currentClip['minY'] * (page.view[3] - page.view[1]) / 600; const maxY = currentClip['maxY'] * (page.view[3] - page.view[1]) / 600; let minX = 0; let maxX = (page.view[2] - page.view[0]); if (currentClip['minX'] != null && currentClip['maxX'] != null) { minX = currentClip['minX'] * (page.view[3] - page.view[1]) / 600; maxX = currentClip['maxX'] * (page.view[3] - page.view[1]) / 600; } const clipImgData = ctx.getImageData(minX, minY, (maxX - minX), (maxY - minY)); canvas1.width = (maxX - minX); canvas1.height = (maxY - minY); ctx.putImageData(clipImgData, 0, 0); // if current image would put total over page height, make new page if ((currentWriteHeight + canvas1.height) > vSize) { pdfWrite.addPage({format: [hSize, vSize]}); currentWriteHeight = 0; } // add image to page pdfWrite.addImage(canvas1, 'PNG', 0, currentWriteHeight, canvas1.width, canvas1.height); currentWriteHeight += canvas1.height; // if this is the last image, show the new pdf file if (x == positionArray.length - 1) { window.open(pdfWrite.output('bloburl'), '_blank'); } }); canvas1.remove(); }); } });
示例数据
fileData = { fileName: "48 Moonrunner.pdf", maxPageHeight: 509.28, maxPageWidth: 315.75359999999995, positionArray: "[0, 19, 20, 2, 3, 22, 21, 14, 15, 16, 17, 11, 9, 4, 5, 8, 12, 13, 10, 6, 18, 7, 1]" } clipData = [ {pageNumber: 1, minY: 0, maxY: 600, minX: 413, maxX: 773}, {pageNumber: 19, minY: 49, maxY: 296, minX: 372, maxX: 721}, {pageNumber: 20, minY: 49, maxY: 360, minX: 0, maxX: 372}, {pageNumber: 27, minY: 438, maxY: 555, minX: 372, maxX: 721}, {pageNumber: 32, minY: 310, maxY: 367, minX: 364, maxX: 715}, {pageNumber: 33, minY: 48, maxY: 107, minX: 0, maxX: 364}, {pageNumber: 36, minY: 324, maxY: 544, minX: 0, maxX: 364}, {pageNumber: 39, minY: 296, maxY: 558, minX: 364, maxX: 721}, {pageNumber: 43, minY: 286, maxY: 417, minX: 0, maxX: 364}, {pageNumber: 59, minY: 59, maxY: 283, minX: 364, maxX: 715}, {pageNumber: 83, minY: 312, maxY: 545, minX: 364, maxX: 715}, {pageNumber: 87, minY: 222, maxY: 552, minX: 364, maxX: 721}, {pageNumber: 91, minY: 0, maxY: 600, minX: 0, maxX: 364}, {pageNumber: 91, minY: 51, maxY: 340, minX: 364, maxX: 715}, {pageNumber: 96, minY: 158, maxY: 382, minX: 0, maxX: 364}, {pageNumber: 104, minY: 53, maxY: 273, minX: 364, maxX: 715}, {pageNumber: 107, minY: 0, maxY: 600, minX: 0, maxX: 364}, {pageNumber: 107, minY: 53, maxY: 444, minX: 364, maxX: 721}, {pageNumber: 112, minY: 331, maxY: 552, minX: 0, maxX: 364}, {pageNumber: 120, minY: 0, maxY: 600, minX: 0, maxX: 364}, {pageNumber: 120, minY: 54, maxY: 446, minX: 364, maxX: 721}, {pageNumber: 123, minY: 284, maxY: 550, minX: 0, maxX: 364}, {pageNumber: 127, minY: 56, maxY: 324, minX: 364, maxX: 721} ]
解决方案
问题根源
循环里嵌套的多层Promise(pdfRead.getPage().then()、page.render().then())不会被for...in循环等待,不同片段的页面加载、渲染速度差异会导致先完成的片段被优先添加到新PDF,完全打乱positionArray指定的顺序。之前尝试的async/await未生效,是因为没有将整个循环逻辑改成异步等待,仅简单包裹async函数却未正确await每个异步操作。
修复步骤
- 将
pdfjsLib.getDocument()的回调改为async函数,支持内部使用await; - 替换
for...in为for...of循环(更适合数组遍历,避免键值问题),确保每次循环都等待所有异步操作完成后再进入下一次; - 将嵌套的
.then()改为await语法,保证每个片段的加载、渲染、裁剪、添加流程按顺序执行; - 所有片段处理完成后统一打开新PDF,取消依赖循环索引判断最后一个片段的逻辑。
修复后的代码
var currentWriteHeight = 0; // 记录当前页面已占用高度,超过阈值则新建页面 const fileName = '../static/upload/' + fileData['fileName']; // 改用async/await处理PDF加载 pdfjsLib.getDocument(fileName).then(async (pdfRead) => { const hSize = fileData['maxPageWidth']; const vSize = fileData['maxPageHeight']; const pdfWrite = new jsPDF({unit: 'px', format: [hSize, vSize]}); const positionArray = JSON.parse(fileData['positionArray']); // 用for...of循环,确保按顺序等待每个片段处理完成 for (const idx of positionArray) { const currentClip = clipData[idx]; const currentPage = currentClip['pageNumber']; // await页面加载完成 const page = await pdfRead.getPage(currentPage); const canvas1 = document.createElement('canvas'); const ctx = canvas1.getContext("2d", { willReadFrequently: true }); const viewport = page.getViewport(1); canvas1.width = viewport.width; canvas1.height = viewport.height; // await页面渲染完成(注意pdfjs的render需取.promise属性) await page.render({ canvasContext: ctx, viewport: viewport }).promise; // 裁剪片段 const pageHeight = page.view[3] - page.view[1]; const minY = currentClip['minY'] * pageHeight / 600; const maxY = currentClip['maxY'] * pageHeight / 600; let minX = 0; let maxX = page.view[2] - page.view[0]; if (currentClip['minX'] != null && currentClip['maxX'] != null) { minX = currentClip['minX'] * pageHeight / 600; maxX = currentClip['maxX'] * pageHeight / 600; } const clipImgData = ctx.getImageData(minX, minY, (maxX - minX), (maxY - minY)); canvas1.width = (maxX - minX); canvas1.height = (maxY - minY); ctx.putImageData(clipImgData, 0, 0); // 判断是否需要新建页面 if ((currentWriteHeight + canvas1.height) > vSize) { pdfWrite.addPage({format: [hSize, vSize]}); currentWriteHeight = 0; } // 添加片段到新PDF pdfWrite.addImage(canvas1, 'PNG', 0, currentWriteHeight, canvas1.width, canvas1.height); currentWriteHeight += canvas1.height; canvas1.remove(); } // 所有片段处理完成后再打开新PDF window.open(pdfWrite.output('bloburl'), '_blank'); });
关键改动说明
- 将
pdfRead.getPage().then()改为await pdfRead.getPage(),等待页面加载完成; - 将
page.render().then()改为await page.render().promise,正确等待页面渲染完成; - 用
for...of遍历positionArray,确保每个片段的处理流程完全结束后再进入下一个; - 移除原代码中依赖索引判断最后一个片段的逻辑,改为循环结束后统一打开新PDF,避免提前触发。
内容的提问来源于stack exchange,提问作者Ian McInnes
相关产品推荐
相关产品推荐

