TypeScript中throw类型收窄失效:互斥返回类型处理疑问
TypeScript处理互斥返回类型的类型断言问题
我尝试创建可处理两种互斥返回类型的函数,但对TypeScript的工作机制存在误解。代码定义了SuccessResponse、ErrorResponse和Command类型,编写myFunc函数时,通过判断result.error !== null并抛出错误后,返回result.data却出现类型错误:Type 'T | null' is not assignable to type 'T'。由于T可以为null,无法仅通过if(result.data)进行判断。此外还提供了包含动机说明的完整示例代码,解释需要错误类型的原因是为了让类型工具断言并存储供后续使用。
问题代码片段
type SuccessResponse<T> = { data: T; error: null }; type ErrorResponse<E> = { data: null; error: NonNullable<E> }; type Command<T, E> = () => ErrorResponse<E> | SuccessResponse<T>; const myFunc = <T, E>(c: Command<T, E>): T => { const result = c(); if (result.error !== null) throw result.error; return result.data; }
错误信息
Type 'T | null' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | null'.(2322)
完整动机示例代码
type SuccessResponse<T> = { data: T; error: null }; type ErrorResponse<E> = { data: null; error: NonNullable<E> }; type Command<T, E> = () => Promise<ErrorResponse<E> | SuccessResponse<T>>; const myCommand = async () => { if (Math.random() < .1) return {data: null, error: new Error('Random error')}; return await { data: Math.random(), error: null}; } const getCommandResult = async <T, E>(c: Command<T, E>): Promise<T> => { const result = await c(); if (result.error !== null) throw result.error; return result.data; } const makeRefreshable = <T, E>(f: () => Promise<T>) => { const result: { data: T | null; error: E | null; run: () => void; } = { data: null, error: null, run: () => {}, } result.run = async () => { try { result.data = await f(); } catch (err) { result.data = null; result.error = err as E; } } return result; } const main = async () => { const result = makeRefreshable<number, Error>(() => getCommandResult(myCommand)); await result.run(); if (result.data) console.log(result.data); if (result.error) console.log(result.error.message); await result.run(); if (result.data) console.log(result.data); if (result.error) console.log(result.error.message); } main();
问题原因
TypeScript无法自动关联SuccessResponse和ErrorResponse中data与error的互斥关系——即使你通过result.error !== null排除了错误情况,它依然无法推断出此时result.data一定不是null。这是因为当前的联合类型没有明确的可辨识特征,TypeScript无法精准缩小类型范围。
解决方案
方案1:使用可辨识联合类型(推荐)
给两种响应类型添加一个共同的可辨识标签字段(比如type),让TypeScript能通过这个标签准确判断当前的响应类型:
type SuccessResponse<T> = { type: 'success'; data: T; error: null }; type ErrorResponse<E> = { type: 'error'; data: null; error: NonNullable<E> }; type Command<T, E> = () => ErrorResponse<E> | SuccessResponse<T>; const myFunc = <T, E>(c: Command<T, E>): T => { const result = c(); if (result.type === 'error') throw result.error; // TypeScript此时能确定result是SuccessResponse<T>,data类型为T return result.data; }
方案2:类型断言(快速修复)
如果无法修改原有类型定义,可以用类型断言直接告诉TypeScript当前result.data的类型:
const myFunc = <T, E>(c: Command<T, E>): T => { const result = c(); if (result.error !== null) throw result.error; return result.data as T; }
注意:类型断言需要开发者自行保证逻辑的正确性,确保此时result.data确实符合T类型。
修正后的完整动机代码(使用可辨识联合)
type SuccessResponse<T> = { type: 'success'; data: T; error: null }; type ErrorResponse<E> = { type: 'error'; data: null; error: NonNullable<E> }; type Command<T, E> = () => Promise<ErrorResponse<E> | SuccessResponse<T>>; const myCommand = async () => { if (Math.random() < .1) return { type: 'error', data: null, error: new Error('Random error')}; return { type: 'success', data: Math.random(), error: null}; } const getCommandResult = async <T, E>(c: Command<T, E>): Promise<T> => { const result = await c(); if (result.type === 'error') throw result.error; return result.data; } const makeRefreshable = <T, E>(f: () => Promise<T>) => { const result: { data: T | null; error: E | null; run: () => void; } = { data: null, error: null, run: () => {}, } result.run = async () => { try { result.data = await f(); } catch (err) { result.data = null; result.error = err as E; } } return result; } const main = async () => { const result = makeRefreshable<number, Error>(() => getCommandResult(myCommand)); await result.run(); if (result.data) console.log(result.data); if (result.error) console.log(result.error.message); await result.run(); if (result.data) console.log(result.data); if (result.error) console.log(result.error.message); } main();
内容的提问来源于stack exchange,提问作者Conley Owens
相关产品推荐
相关产品推荐

