TypeScript API Client:统一请求边界和错误语义

背景 每个页面各写一遍 fetch,最终会产生不同的超时、错误和 JSON 解析行为。统一客户端是把网络不确定性收敛到一个边界。 核心判断 统一取消、超时、HTTP 状态与响应解析。 领域接口接受运行时解码器,不靠 request 断言服务端数据。 区分网络失败、HTTP 拒绝和响应契约错误,供界面选择恢复策略。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 async function request<T>( input: RequestInfo, decode: (value: unknown) => T, ): Promise<T> { const response = await fetch(input, { signal: AbortSignal.timeout(5000), }); if (!response.ok) throw new ApiError(response.status); const value: unknown = await response.json(); return decode(value); } 边界与误区 AbortSignal.timeout 需要目标运行环境支持。204 无响应体、非 JSON 错误页和调用方取消也要处理;示例适用于约定 JSON 正文的接口。 验证方式 用 mock server 覆盖慢响应、401、500、畸形 JSON 和错误字段。检查同一错误类型在不同页面的提示与重试策略保持一致。 小结 客户端统一处理超时、取消、解析和错误,领域层只暴露稳定的方法和错误分类。

2026年9月30日 · 1 分钟 · BvBeJ

TypeScript 运行时边界:类型声明不能替代输入校验

背景 给 JSON.parse 的结果加 User 类型,不会自动检查字段。类型系统接收到不可信数据之前,需要一道真正执行的校验。 核心判断 HTTP、消息与本地存储统一从 unknown 开始。 在边界执行 schema 校验,成功后才交给领域层。 转换、默认值与未知字段策略显式声明,不默默修改业务含义。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 const User = z.object({ id: z.number().int(), name: z.string().min(1) }); const user = User.parse(JSON.parse(body)); 边界与误区 parse 会抛出异常;接口需要可控错误返回时可以用 safeParse。示例依赖 zod,并且 JSON 语法错误与 schema 错误应分别分类。 验证方式 覆盖缺字段、错误类型、空字符串、超范围数字和畸形 JSON,确认输入错误返回客户端错误,而不是落入通用 500。 小结 HTTP、消息和存储数据进入类型系统前都不可信,边界必须执行 schema 校验。

2026年9月20日 · 1 分钟 · BvBeJ

Rust 借用 API 设计:把生命周期约束藏在正确边界

背景 解析函数返回输入切片通常很自然,但把数据库连接或内部缓存的生命周期暴露出去,会让调用方被迫理解实现细节。 核心判断 只有结果确实借用输入时,才把生命周期关系写进 API。 跨任务、缓存或异步队列的结果通常需要拥有数据。 优先让编译器推断生命周期,不为形式完整添加无意义参数。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 pub fn split_at<'a>(input: &'a str, sep: char) -> Option<(&'a str, &'a str)> { input.split_once(sep) } 边界与误区 示例中返回值确实指向 input;separator 是值,不参与借用关系。一次小复制可能换来更简单的长期接口,不应只追求分配次数。 验证方式 编写结果不能超过输入存活时间的编译失败测试;再测长期保存结果的场景,确认必要的 owned 转换发生在清晰边界。 小结 公共 API 只暴露真实需要的借用关系,避免为了少一次分配泄漏内部生命周期。

2026年9月12日 · 1 分钟 · BvBeJ