typescript - 对于总是抛出异常的函数返回 `never` 只有在它不是对象属性时才会缩小类型
假设你有一个会抛出特定配置错误的函数:
function throwMyError(): never {
throw new Error("My Custom error")
}
并且你有一个可为空的值:
declare const str: string | null
你可以像这样将该值缩小为非空类型:
if(!str) throwMyError()
str.toLocaleLowerCase() // fine
现在将该函数调用放入对象字面量中,它就不再能够进行相同的缩小操作了:
const foo = { throwMyError }
if (!str) foo.throwMyError()
str.toLocaleLowerCase() // 'obj' is possibly 'null'.(18047)
或者:
const bar = {
throwMyError: (): never => {
throw new Error()
}
}
if (!str) bar.throwMyError()
str.toLocaleLowerCase() // 'obj' is possibly 'null'.(18047)
为什么当从对象类型调用抛出函数时,缩小不起作用?
可以在不创建类型谓词函数的情况下解决这个问题吗?
查看 TypeScript Playground
相关问题:类型缩小与 never 函数
但这似乎并没有解决这个问题,因为在所有情况下,函数类型都明确标注为返回 never
答案#1
在实现的 PR microsoft/TypeScript#32695 中,明确说明了将 never 类型返回的函数视为影响控制流分析的断言的条件。看起来是这样的:
当函数调用被分析为断言调用或永不返回的调用时
该调用作为顶层表达式语句发生,且
调用为函数名指定单个标识符或由点分隔的标识符序列,并且
函数名中的每个标识符都引用具有显式类型的实体,并且
函数名解析为具有
asserts返回类型或显式never返回类型注解的函数类型。当一个实体被声明为函数、方法、类或命名空间,或者作为具有显式类型注释的变量、参数或属性时,认为它具有显式类型。(这个特定规则的存在是为了防止潜在断言调用的控制流分析循环触发进一步的分析。)
你在 foo 和 bar 上遇到的问题是,它们都是没有显式类型注解的变量。你允许编译器推断它们的类型,在几乎所有其他情况下,这是完全可以接受且合理的。但显然,对于断言函数和返回 never 的函数来说,支持这种做法太困难;据说这会导致类型检查器变慢并产生循环警告。
如果你希望 foo 和 bar 能正常工作,那么你需要像这样明确地给它们添加注解:
declare const str: string | null;
const foo: { throwMyError(): never } = { throwMyError };
if (!str) foo.throwMyError();
str.toLocaleLowerCase(); // okay
或
declare const str: string | null;
const bar: { throwMyError(): never } = {
throwMyError: (): never => { throw new Error() }
};
if (!str) bar.throwMyError();
str.toLocaleLowerCase(); // okay
这是你想要的行为,对于这里的示例代码来说,额外的工作量并不大。但在实际操作中,这种对类型注解的限制使得使用never返回类型和断言方法成为一项重大负担,因为你会发现自己需要为本来可以方便地匿名并由系统推断的类型起显式名称。根据使用情况,这可能只是一个小小的烦恼(const foo: Foo=⋯),也可能成为完全的阻碍(const baz: Baz<Map<string, boolean>, "qux", [number, "hello"], Baz<Map<string, Date>, "quux", [boolean, "goodbye"], ⋯>>=⋯)。因此请谨慎操作。
代码的 Playground 链接
