问题复现
最近在开发一个 AI 问答小程序时,由于接口返回的是流式二进制数据,因此我使用了 TextDecoder 的 decode 方法将二进制数据转换为文本。在开发环境中,数据处理一直没有问题,但在真机测试及上线后,发现调用接口时出现了 TextDecoder is not defined 的报错,导致数据无法正常显示。最终发现,问题出在 TextDecoder 这个 Web API 在小程序的生产环境中并不兼容。
解决方案
一开始,我尝试了很多解决 TextDecoder 兼容性问题的库,例如 text-encoding、text-encoding-polyfill 和 buffer,但都没有完全解决问题。最后,我决定手写一个 TextDecoder 的实现,并将其挂载到全局。废话不多说,直接上代码,各位大哥直接复制粘贴即可。
// TextDecoder polyfill
if (typeof TextDecoder === 'undefined') {
class TextDecoder {
constructor(encoding = 'utf-8') {
this.encoding = encoding.toLowerCase();
}
decode(dataView) {
let data;
if (dataView instanceof ArrayBuffer) {
data = new Uint8Array(dataView);
} else if (dataView instanceof Uint8Array) {
data = dataView;
} else {
throw new Error('参数必须是 ArrayBuffer 或 Uint8Array');
}
if (this.encoding === 'utf-8') {
return this._decodeUTF8(data);
} else {
throw new Error('当前只支持 UTF-8 编码');
}
}
_decodeUTF8(data) {
let str = '';
let i = 0;
while (i < data.length) {
let byte1 = data[i];
let char;
// ASCII 字符
if (byte1 < 0x80) {
char = String.fromCharCode(byte1);
i += 1;
}
// 2字节序列
else if (byte1 < 0xE0) {
const byte2 = data[i + 1];
char = String.fromCharCode(((byte1 & 0x1F) << 6) | (byte2 & 0x3F));
i += 2;
}
// 3字节序列
else if (byte1 < 0xF0) {
const byte2 = data[i + 1];
const byte3 = data[i + 2];
char = String.fromCharCode(
((byte1 & 0x0F) << 12) |
((byte2 & 0x3F) << 6) |
(byte3 & 0x3F)
);
i += 3;
}
// 4字节序列
else {
const byte2 = data[i + 1];
const byte3 = data[i + 2];
const byte4 = data[i + 3];
let codepoint = ((byte1 & 0x07) << 18) |
((byte2 & 0x3F) << 12) |
((byte3 & 0x3F) << 6) |
(byte4 & 0x3F);
// UTF-16 surrogate pair
codepoint -= 0x10000;
const highSurrogate = (codepoint >> 10) + 0xD800;
const lowSurrogate = (codepoint & 0x3FF) + 0xDC00;
char = String.fromCharCode(highSurrogate, lowSurrogate);
i += 4;
}
str += char;
}
return str;
}
}
// 全局注入 TextDecoder
globalThis.TextDecoder = TextDecoder;
}
新建一个 textDecoder.js 文件,将自定义的 TextDecoder 代码放入其中。然后在 app.js 中通过 import 进行导入。这样,我们的 TextDecoder 就可以在生产环境中正常使用了。
// app.js
const request = require('./utils/request')
import './utils/textDecoder'
App({
onLaunch() {
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登录
request.login();
},
globalData: {
userInfo: null
}
})