跳到主要内容

1 篇博文 含有标签「Promise」

查看所有标签

微信小程序识图全失败?wx Promise 包装的 [object Object] 坑

· 阅读需 7 分钟

在小程序里点识图按钮上传一张图片时,无论 devtools 还是真机、无论传什么图,识别全部失败——服务端只回一句「图片识别失败,请稍后重试」。

在开发 Life 记账助手 时遇到此问题——自然语言记账健康助手,拍照识图是它的记账入口之一。这个坑的麻烦不在修(一行的事),而在定位:每一层报错都在说谎。

TL;DR​

  • 根因:自己写的 wx API Promise 包装器用 success: resolve 直接把回调结果 resolve 了——wx.readFile 的成功结果是 { data, errMsg } 整体,不是 data 本身。下游 String(b64) 把整个对象转成 "[object Object]",剥空白后剩 14 字符伪 base64,解码出 9 字节固定垃圾发给了服务端。
  • 修法:resolve 之后点属性取值(res.data),并对非字符串抛错防御。
  • 定位经验:通用报错体(invalid params)不泄漏来源时,尽早加发送侧形状诊断日志(长度/字符集/魔数,零隐私内容),胜过反复假设实验。

现象:100% 失败,且与图片内容无关​

失败模式是 100% 必现且与图片内容完全无关——这指向发送侧的数据本身,而不是识别能力。

识图按钮流的链路是:选图 → 压缩 → 读成 base64 → 发给服务端 → 服务端转交视觉模型。故障表现非常整齐:devtools 和真机一致、每一次都失败。服务端日志里只有视觉网关的一条 400:

POST /vision  400  {"error": "invalid params"}

invalid params 是个通用错误体——它不告诉你哪个参数、错成什么样。而我们的路由层把网关失败统一包装成 503「图片识别失败,请稍后重试」,用户看到的和日志里的信息一样少。

排查弯路:两个被证伪的假设​

通用报错体会把排查引向「参数格式规范」方向的猜测——本文的两段弯路都源于此。

假设一:mime 类型不标准。 我们最初怀疑压缩产物用了非标的 image/jpg(标准写法是 image/jpeg)。对着网关直接实测三种 mime,全部返回 200——证伪。图片本身和它的描述信息都没问题,问题在更下游。

假设二:base64 折行。 第二个假设是 wx.readFile 产出的 base64 带换行符,某些网关不接受。我们往测试请求里注入 \n 复现出了同一个错误体——注意,这是个假阳性:通用报错意味着任何非法 payload 都长一个样,注入 \n 复现成功根本不能证明线上就是折行问题。我们还是上了双端剥空白(.replace(/\s+/g, '')),上线后复测——依然全挂。假设二证伪,但剥空白的防御代码留了下来(它本身无害)。

两轮假设都错,因为它们都在「猜参数格式」,而真正的故障模式是发送的东西根本不是图片。

根因:包装器 resolve 了整个回调结果对象​

wx.readFile 的 success 回调结果是整体一个对象 { data, errMsg },包装器把它整个 resolve 了下去——下游拿到的从来不是 base64。

假设耗尽后换了打法:在服务端失败路径加形状诊断日志——只记 payload 的元数据(长度、mime、空白、字符集、解码后字节数、魔数),不记内容,零隐私风险。下一轮复测,日志直接暴露真相:

b64Len: 14   charsetOk: false   decodedBytes: 9   magicHex: a1b8de72

一张图的 base64 至少几万字符,这里只有 14;解码出 9 字节固定垃圾,每次请求的魔数都是 a1b8de72。再对齐一个关键线索:两次不同图片的请求 payload 逐字节相同——固定串,不是真实图片。14 字符的固定串是什么?"[object Object]" 剥掉空格后的 "[objectObject]",正好 14 个字符。

回到代码,链路每一环都「看起来对」:

// 通用包装器:把回调风格 API 转成 Promise
const p = (fn) => (opts) =>
new Promise((resolve, reject) => fn({ ...opts, success: resolve, fail: reject }))

// 读文件成 base64(修复前)
const readFileBase64 = (filePath) =>
p(wx.getFileSystemManager().readFile.bind(wx.getFileSystemManager()))({
filePath,
encoding: 'base64',
}).then((b64) => String(b64))

wx.readFile 的 success 回调结果是整体一个对象 { data, errMsg }。包装器 success: resolve 把这整个对象 resolve 了下去;.then((b64) => String(b64)) 拿到的是对象,String() 把它转成 "[object Object]"——全程零报错。剥空白、编码、发送,一路畅通,直到网关用一句通用 400 把它拒收。

Node 的 base64 解码还会忽略非法字符([ 和 ]),于是 12 个合法 base64 字符解出 9 字节垃圾——和诊断日志逐字吻合。

修复:点属性取值 + 抛错防御​

修复就两件事:resolve 之后的对象点属性取值(res.data),取出的值不是非空字符串就直接抛错。


```js
const readFileBase64 = (filePath) =>
p(wx.getFileSystemManager().readFile.bind(wx.getFileSystemManager()))({
filePath,
encoding: 'base64',
}).then((res) => {
const data = res && res.data
if (typeof data !== 'string' || !data) throw new Error('图片读取失败')
return data
})

抛错防御的价值在坏数据出现的瞬间就体现:前端拿到明确的错误信息,而不是千里之外一句通用 400——坏数据死在源头,每一层都省一次猜谜。

修复本身很小,commit 连注释一共 +8/-1 行。修完复测,devtools 与真机识图全部恢复,与图片内容无关的必现故障归零。

注意事项

写 wx API 的 Promise 包装器时,resolve 的对象必须点属性取值——不同 API 的回调结果形状不同:readFile 是 .data、chooseMedia 是 .tempFiles、compressImage 是 .tempFilePath,逐个确认,不要写一个「通用 then」假设形状。String(某对象) 永远静默产出 "[object Object]",不抛错、不告警;对通用 invalid params 类报错,反复假设实验的成本远高于一条发送侧形状日志(只记长度/字符集/魔数等元数据,零隐私内容)。

常见问题​

小程序图片识别失败怎么办?​

先确认失败是不是 100% 必现且与图片内容无关——是的话大概率是发送侧数据本身错了,不是识别能力问题。给发送侧加形状诊断日志(payload 长度、字符集、解码后字节数、魔数,零隐私内容),本案例靠它定位到 base64 实为 14 字符垃圾串。

小程序图片识别失败是怎么回事?​

最隐蔽的一类是数据在发送前就被静默替换:wx API 的 Promise 包装器用 success: resolve 会把整个回调结果对象 { data, errMsg } 当值传下去,String() 化成 "[object Object]"、剥空白后剩 14 字符伪 base64,解码只有 9 字节固定垃圾,服务端只能报 400。

wx.readFile 读出来的数据不对怎么办?​

检查你的 Promise 包装器:回调成功结果要取 res.data 再往下传,不能把整个对象 String() 硬转。同时加一道防御——取出的值不是非空字符串就直接抛错,让坏数据死在源头,比到服务端再看通用报错快得多。

CCLEE

独立开发者,24年电商行业实战经验,专注将AI能力落地于真实商业场景。

合作咨询