DEBUG.WIKI
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →

Ky 2 错误钩子读 JSON 报 Already read

升级 Ky 2 后,beforeError 再读 response.json 可能遇到已消费的响应体。改用 error.data,并按 HTTPError、空数据和错误字段类型分别处理。

从 Ky 1 升级到 Ky 2,服务器仍返回原来的错误 JSON,但集中处理错误的钩子开始报 TypeError: Already read。如果代码在 beforeError 中再次调用 error.response.json(),应先改掉这一处读取方式。

Ky 2 会在调用错误钩子前读取 HTTP 错误响应,将结果放入 error.data。响应流已经被消费,再从同一 Response 读取 JSON,自然无法得到第二份正文。具体运行时也可能使用“body already used”一类措辞。

错误正文已经放在 data 里

这里的变化适用于 HTTPError。网络连接失败、请求取消和其他错误,并不保证存在 response。旧代码若直接访问 error.response,也可能掩盖原来的异常。

正式 Ky 2.2.0 中,beforeError 接收包含 error 等字段的状态对象。下面的示例只在响应数据确实带有字符串错误信息时替换 message,其余信息保留原值:

import ky, { isHTTPError } from 'ky';

const api = ky.create({
  hooks: {
    beforeError: [
      ({ error }) => {
        if (isHTTPError(error)) {
          const data = error.data;
          if (data !== null && typeof data === 'object'
              && 'error_message' in data
              && typeof data.error_message === 'string') {
            error.message = data.error_message;
          }
        }
        return error;
      },
    ],
  },
});

error_message 是这个示例约定的服务端字段,需要替换成接口真实协议。不要把整个响应对象转换成字符串塞进 message;那既可能失去可读性,也可能把敏感业务数据写进错误日志。

旧式的 async error => ... 也要一并核对。不能只把 response.json() 改成 data,却把 Ky 2 传入的整个状态对象继续当成 Error。

data 不保证是某个对象

Ky 根据响应内容类型解析错误正文。JSON 可能得到对象,也可能是数组、数字或 null;其他内容类型可能得到文本。空响应、解析失败或无法读取时,data 可以是 undefined。TypeScript 中给 HTTPError 指定泛型,只影响静态类型,不会替服务器验证返回值。

如果原来还需要状态码或响应头,可以继续从 HTTPError 的 response 读取这些元数据。此时调用 response.clone().json() 也不能让已经消费的正文恢复:克隆必须发生在消费之前,不能当成这里的补救。

Ky 2.2.0 的错误数据读取有大小和时间边界。超大错误页或一直不结束的流,不能被当成必然可用的 JSON。保留没有业务 message 时的默认异常,不要为了取错误说明取消原来的超时或无限缓存响应体。

用同一组错误响应验收迁移

在应用原来的请求封装中检查 JSON 错误对象、非 JSON 错误页、空错误正文和网络失败。前者应读到约定信息,后几种仍应返回可识别的错误,不能再被二次读取异常覆盖。

若某些接口使用 throwHttpErrors: false,非成功状态不会走同样的 HTTPError 路径,应沿原有的状态判断处理。此次调整只处理错误数据的消费与钩子签名,不改变重试、认证或成功响应的解析方式。