Hono 4.12.8 调用 parseBody 后,text() 报 then is not a function
Hono 4.12.8 对非表单请求执行 parseBody 后,普通对象可能进入 Promise 缓存,使下游 text/json 报错。采用含正式修复的版本,并限定表单中间件的解析范围。
中间件先执行 c.req.parseBody(),下游再调用 c.req.text(),Hono 4.12.8 可能抛出:
TypeError: bodyCache[anyCachedKey].then is not a function
一个容易误判的条件是:请求根本不是表单。JSON 或没有 Content-Type 的请求,parseBody() 可以先返回空对象,随后读原文才出错。这与原生 Response 被 text/json 消费两次不是同一条处理路径。
空表单结果进入了只应放 Promise 的缓存
4.12.8 会把 parseBody() 的已解析对象写进 bodyCache.parsedBody。对于非表单请求,这个对象可能成为缓存里的第一项。随后 text/json 的缓存逻辑把它当作 Promise 调用 .then(),于是报错。
真实表单会先经过 formData(),缓存中的第一项通常已经是 Promise,所以同一套中间件可能在表单请求上正常、在 JSON webhook 上失败。给空对象加一个 .then,或手工把字符串写入内部 bodyCache,都不适合处理这个问题,会破坏其他正文读取方法的约定。
正式 4.12.9 移除了多余的 parsedBody 缓存;正式 4.13.0 也保留了这项修正。处理旧项目时,先核对实际解析到的 Hono 版本,再沿项目的依赖约束采用包含修复的受维护兼容版本。不要只修改 package.json 的范围,留下仍锁在 4.12.8 的构建产物。
npm ls hono
表单中间件只处理表单
暂时不能更新依赖时,可以让现有表单中间件先判断 Content-Type,避免对 JSON 请求无条件执行 parseBody。下面是合入已有 Hono 应用的片段,app 指原来的实例:
app.use('*', async (c, next) => {
const type = (c.req.header('content-type') ?? '')
.split(';', 1)[0]
.trim()
.toLowerCase();
if (
type === 'multipart/form-data' ||
type === 'application/x-www-form-urlencoded'
) {
await c.req.parseBody();
}
await next();
});
这个片段只展示解析分支,不会自动把表单结果写入业务上下文。已有业务若依赖这个结果,应保留原来的赋值与类型声明。也不要把 Content-Type 一律改成表单来迁就中间件,或把不认识的正文猜成 JSON。
对于需要原始正文验签的 webhook,要保留供应商要求的原始字节、编码和验证顺序。不要先解析 JSON 再 stringify 成另一份字符串验签;正文顺序、空白等变化都可能影响签名。这里修复的是读取入口与缓存问题,并没有提供或替代签名验证。
验收覆盖原来会失败的 JSON 请求、没有 Content-Type 的请求,以及两种真实表单。确认下游能读取预期内容,表单字段仍完整,非法格式仍按原规则被拒绝。若用的是 c.req.raw 的原生读取方法,还要单独检查正文是否已消耗;Hono 的缓存修复不会让原生流可以任意重复读取。遇到这种情况,可继续核对正文只消费一次的处理方式。


