最近的移动端项目中需要使用 PDFjs 展示 PDF 文件,项目完成开发后在测试中发现,在浏览器或微信中访问时都正常,而在 QQ 中访问会报 undefined is not an object (evaluating ‘response.body.getReader’)
错误。
问题排查
既然只有 QQ 报错,说明不是业务代码的问题,于是打开 PDFjs 的源码,搜索之后发现在两个 class
中有这段代码,一个是 PDFFetchStreamReader
,另一个是 PDFFetchStreamRangeReader
,我使用的是 PDFFetchStreamReader
,源码如下:
class PDFFetchStreamReader {
constructor(stream) {
this._stream = stream;
this._reader = null;
this._loaded = 0;
this._filename = null;
const source = stream.source;
this._withCredentials = source.withCredentials || false;
this._contentLength = source.length;
this._headersCapability = (0, _util.createPromiseCapability)();
this._disableRange = source.disableRange || false;
this._rangeChunkSize = source.rangeChunkSize;
if (!this._rangeChunkSize && !this._disableRange) {
this._disableRange = true;
}
this._abortController = new AbortController();
this._isStreamingSupported = !source.disableStream;
this._isRangeSupported = !source.disableRange;
this._headers = createHeaders(this._stream.httpHeaders);
const url = source.url;
fetch(url, createFetchOptions(this._headers, this._withCredentials, this._abortController)).then(response => {
if (!(0, _network_utils.validateResponseStatus)(response.status)) {
throw (0, _network_utils.createResponseStatusError)(response.status, url);
}
this._reader = response.body.getReader();
this._headersCapability.resolve();
const getResponseHeader = name => {
return response.headers.get(name);
};
const {
allowRangeRequests,
suggestedLength
} = (0, _network_utils.validateRangeRequestCapabilities)({
getResponseHeader,
isHttp: this._stream.isHttp,
rangeChunkSize: this._rangeChunkSize,
disableRange: this._disableRange
});
this._isRangeSupported = allowRangeRequests;
this._contentLength = suggestedLength || this._contentLength;
this._filename = (0, _network_utils.extractFilenameFromHeader)(getResponseHeader);
if (!this._isStreamingSupported && this._isRangeSupported) {
this.cancel(new _util.AbortException("Streaming is disabled."));
}
}).catch(this._headersCapability.reject);
this.onProgress = null;
}
get headersReady() {
return this._headersCapability.promise;
}
get filename() {
return this._filename;
}
get contentLength() {
return this._contentLength;
}
get isRangeSupported() {
return this._isRangeSupported;
}
get isStreamingSupported() {
return this._isStreamingSupported;
}
async read() {
await this._headersCapability.promise;
const {
value,
done
} = await this._reader.read();
if (done) {
return {
value,
done
};
}
this._loaded += value.byteLength;
if (this.onProgress) {
this.onProgress({
loaded: this._loaded,
total: this._contentLength
});
}
const buffer = new Uint8Array(value).buffer;
return {
value: buffer,
done: false
};
}
cancel(reason) {
if (this._reader) {
this._reader.cancel(reason);
}
this._abortController.abort();
}
}
getReader
是 ReadableStream
的一个方法,用于创建一个 reader
,具体内容可以查看 MDN 文档。
该方法调用位置的前后关系大意是对返回的 ReadableStream
创建一个 reader
,作为渲染到 canvas
中的数据。
打印 response.body
得到的内容如下:
看起来什么问题都没有,但就是报错,那就说明是 QQ 的问题了。
解决办法
虽然知道了是 QQ 的问题,但我也没法处理,只能想想变通的办法了。
仔细分析源码后发现,PDFjs 的 getDocument
方法不仅可以接收 URL
作为参数,还可以接收多种类型:
而 fetch
方法返回的 Response
对象恰恰拥有 arrayBuffer
方法,可以将数据转为 ArrayBuffer
对象。
所以,解决的方法就是,判断是否处于 QQ 环境中,如果是,则先使用 fetch
获取 PDF 文件并转为 ArrayBuffer
,再传入 getDocument
方法中:
const src = `https://www.example.com/example.pdf`;
let arrayBufferPDF;
if (navigator.userAgent.indexOf("QQ") > -1) {
const pdfData = await fetch(src);
arrayBufferPDF = await pdfData.arrayBuffer();
}
const loadingTask = arrayBufferPDF ? pdfjsLib.getDocument({data: arrayBufferPDF}) : pdfjsLib.getDocument(src);
loadingTask.promise.then(async (pdf) => {}).catch(() => {});
如此操作,就能解决问题了。
fetch
方法的更多信息可以查看 MDN 文档。
结语
虽然问题解决了,但是至于为什么 QQ 会出现这样的问题,我依旧匪夷所思。