复杂泛型异步返回类型无法推断具体返回值问题求助
重构TypeScript通用API处理器以实现精确类型推断
核心问题分析
原代码中泛型参数未与处理器的输入输出强绑定,导致错误码被宽泛推断为string,成功数据被推断为通用的Jsonifiable而非具体结构。本质是泛型推导链条断裂,无法从业务逻辑中捕获细化的类型信息。
重构方案
1. 定义判别式联合响应类型
先为成功/错误响应定义带类型标记的严格结构,让TypeScript能自动分支细化:
// 错误响应:绑定具体错误码字面量 type ApiError<Code extends string> = { type: "error"; code: Code; message?: string; }; // 成功响应:绑定具体数据结构 type ApiSuccess<Data> = { type: "success"; data: Data; }; // 统一API响应类型:通过type字段做判别式 type ApiResponse<Data, ErrorCode extends string> = | ApiSuccess<Data> | ApiError<ErrorCode>;
2. 让处理器与业务配置强绑定
将API的业务逻辑、错误映射作为泛型参数的推导源,确保TypeScript能从配置中捕获精确类型:
// API处理器配置类型 type ApiHandlerConfig<Data, ErrorCode extends string> = { // 业务处理函数,返回具体成功数据或抛出异常 handler: () => Promise<Data>; // 异常信息到错误码的映射,必须用as const断言锁定字面量 errorMap: Readonly<Record<string, ErrorCode>>; }; // 通用API处理器 async function handleApi<Data, ErrorCode extends string>( config: ApiHandlerConfig<Data, ErrorCode> ): Promise<ApiResponse<Data, ErrorCode>> { try { const data = await config.handler(); return { type: "success", data }; } catch (err) { const errorKey = err instanceof Error ? err.message : String(err); const code = config.errorMap[errorKey] ?? ("unknown" as ErrorCode); return { type: "error", code }; } }
3. 调用时自动推断精确类型
传入配置时,通过as const锁定错误码字面量,让TypeScript自动推导出细化的响应类型:
// 示例API:数字校验 const result = await handleApi({ handler: async () => { const num = Math.random() * 100; if (num > 80) throw new Error("too big"); if (num < 20) throw new Error("too small"); return { foo: "bar" }; // 成功数据的具体结构 }, errorMap: { "too big": "too big", "too small": "too small" } as const // 关键:强制推断为字面量类型而非string }); // 此时result的类型为: // { type: "success"; data: { foo: string; }; } // | { type: "error"; code: "too big" | "too small"; } // 穷尽处理所有场景(TypeScript会强制检查未覆盖分支) switch (result.type) { case "success": console.log(result.data.foo); // 类型安全,可直接访问foo break; case "error": switch (result.code) { case "too big": console.log("数值超过上限"); break; case "too small": console.log("数值低于下限"); break; // 新增错误码时,TypeScript会提示未穷尽处理 } }
关键优化点
as const字面量断言:确保错误码被推断为具体字符串,而非宽泛的string类型。- 判别式联合类型:通过
type字段让TypeScript在分支中自动缩小类型范围,实现类型安全的穷尽检查。 - 泛型推导绑定:处理器的泛型参数直接从
handler返回值和errorMap类型推导,无需手动指定。
进阶优化:自定义异常类强化类型约束
如果希望错误码与异常强绑定,可自定义异常类省略errorMap:
class ApiException<Code extends string> extends Error { constructor(public code: Code, message?: string) { super(message); } } // 简化后的处理器 async function handleApi<Data, ErrorCode extends string>( handler: () => Promise<Data> ): Promise<ApiResponse<Data, ErrorCode>> { try { const data = await handler(); return { type: "success", data }; } catch (err) { if (err instanceof ApiException) { return { type: "error", code: err.code }; } return { type: "error", code: "unknown" as ErrorCode }; } } // 使用示例 const result = await handleApi(async () => { const num = Math.random() * 100; if (num > 80) throw new ApiException("too big", "数值超限"); if (num < 20) throw new ApiException("too small", "数值过低"); return { foo: "bar" }; }); // 错误码自动推断为"too big"|"too small"
内容的提问来源于stack exchange,提问作者osdiab
相关产品推荐
相关产品推荐

