标题也可以改成:pdf.js 使用 fetch, axios 来渲染获取渲染内容
一、问题背景描述
在日常工作中,使用 pdf.js 做 文件的预览,是一个很常见的需求!
而且这个组件非常强大,背后的维护组织是:mozilla,火狐浏览器的组织,所以非常的强大!
这里放一段 pdf.js 官方 github 仓库的示例代码:
const pdfPath = "../learning/helloworld.pdf";
// Setting worker path to worker bundle.
pdfjsLib.GlobalWorkerOptions.workerSrc =
"../../build/browserify/pdf.worker.bundle.js";
// Loading a document.
const loadingTask = pdfjsLib.getDocument(pdfPath);
loadingTask.promise
.then(function (pdfDocument) {
// Request a first page
return pdfDocument.getPage(1).then(function (pdfPage) {
// Display page on the existing canvas with 100% scale.
const viewport = pdfPage.getViewport({ scale: 1.0 });
const canvas = document.getElementById("theCanvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext("2d");
const renderTask = pdfPage.render({
canvasContext: ctx,
viewport,
});
return renderTask.promise;
});
})
.catch(function (reason) {
console.error("Error: " + reason);
});
代码不多,使用方式也是很简单:
- 把 pdf.js 引进来;
- 设置一下worker,在后台解析 pdf;
- 然后就是加载一个 PDF 文档;
- 然后就是面向 pdf.js 提供的 API 编程,把内容通过 canvas 渲染展示给用户(我们平时应该更关心的是这个步骤,中途还会做很多屏幕,缩放的适配,懒加载,内存的回收 …)
- …
一切都很美好,直到遇到:手机QQ,QQ浏览器
本来在其他地方已经调试的好好的东西,在这两个平台上莫名其妙的挂了,还报了一个错误。大致是这样的:
贴一个文字版的:
n { message: “undefined is not an object (evaluating ‘e.body.getReader’)”, name: “UnknownErrorException”, details: “TypeError: undefined is not an object (evaluating ‘e.body.getReader’)” }
二、问题定位
是不是看着很迷惑,很少见过这种报错对吧!
这个是从 pdf.js 里面报出来的,其实跟业务代码没有关系。那这个时候你可能就想了,既然不是我们的代码,我们能通过自己的努力解决吗?
答案是可以的,不然也不会有这个文章!
需要解决这个问题,那就得先了解为什么会出现这个问题。
刚刚也说过,我们在其他浏览器是没有问题的,怎么突然换到 QQ 上就不行了呢?一个念头闪过:兼容性 !
这个问题必须是兼容性问题。那在定位一下,到底是哪个API出了问题呢?
说实话,这个就不好确定了。这里其实找了很久也没有确定,就只能跟着报错的信息一步一步找!直接把 pdf.js 的源码 clone 下来,自己在源码里面看!
那既然报错是:TypeError: undefined is not an object (evaluating ‘e.body.getReader’)
大概就能猜出来:body.getReader
应该是这里出问题了,肯定有一个是 undefined
那就到源代码里面找:
这里可以大概解释一下,这里主要是 pdf.js 加载文件的地方,可以看到其实两个报错的位置都是在加载文件!
所以聪明的你,一下应该就能猜到答案,兼容性问题就出在这里吧 - Fetch + ReadableStream
这里主要就是使用了 Fetch 加载文档,然后 pdf.js 从接口中流式读取内容!
如果不是很熟悉这几个API的可以看看:
反复尝试,确定问题就是出现在这里!
三、解决方案
问题找到了,但是我们可以改变框架的代码吗?
答案是不能的,但是我们可以给初始化的逻辑改了。
从 getDocument
入口函数我们可以看到:
pdf.js 是支持我们传递一个 pdf 的二进制数据作为初始化的!
我们可以从这里入手。而且也分析过,我们报错的是因为加载 pdf 文件导致的。所以只要在入口控制住,不让 pdf.js 走 URL 的方式初始化那就不会出问题!
解决思路就是:我们可以通过 fetch 手动去请求 pdf 文件内容,然后通过 ArrayBuffer 的方式再塞给 pdf.js
这里就是八仙过海各显神通了!大家可以自己行实现。我把自己的做法贴出来,仅供参考!
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf';
let pdfDoc;
try {
pdfDoc = await pdfjsLib.getDocument(url).promise;
} catch (error) {
if (String(error).indexOf('response.body.getReader') > -1) {
// 某些浏览器内核不支持 Fetch - response.body.getReader 的模式,专门在这里修复
const pdfData = await fetch(
new URL(url, window.location).href,
);
const arrayBufferPdf = await pdfData.arrayBuffer(); // 转成 ArrayBuffer 塞给 pdf.js
pdfDoc = await pdfLib.getDocument({ data: arrayBufferPdf }).promise;
}
}
// pdf render ...
经过以上的代码,我们在获取PDF内上,就不会有报错问题的存在了,后面的渲染逻辑跟以前是一样的,就不再次赘述!
四、思维拓展
经过了这一番折腾,回过头来看,其实解决问题的方式很简单,如果能早一点想到就好了。
在这里,也把自己想到的一个场景记录下来:展示内存中的PDF文件
大概的意思就是:如果我们想让用户随便选择一个 pdf 文件,我们给他显示出来。如果你看懂了上面的解决方案,那这个需求就很容易实现:
- 声明一个 input 框,类型是
file
- 在 onchang 的时候,如果有内容,就
new FileReader()
初始化一个 Reader,读取文件内容 - Reader 返回的是一个二进制的内容,我们在转一下,变成
Uint8Array
再塞给 pdf.js 就解决问题了!
大家感兴趣可以看公众号: