TypeScript中使用throwIf函数时的类型错误解决方法(不改返回类型)
首先得说,你遇到的问题本质是TypeScript没法识别你的throwIf函数能起到「终止代码执行并抛出错误」的作用,所以它没法确定cinema一定不是undefined。而且其实你原来的throwIf函数还有个隐藏的逻辑漏洞——它只是返回了一个Error对象,但根本没抛出,就算cineId或者database是null,代码还是会继续跑下去,这其实是有问题的。
先看你原来的可编译代码:
function throwIf(condition: boolean, message: string){ if (condition) return new Error(message) } function getCinema(cineId: number, database: Database): Cinema { throwIf(cineId == null, 'cineId should not be null') throwIf(database == null, 'database should not be null') let cinema = database.find(cinema => cinema.id === cineId) if (!cinema) throw new Error(`No cinema with id : ${cineId}`) return cinema }
这段代码能编译通过,是因为你用if (!cinema) throw ...的时候,TypeScript明确知道如果cinema是undefined,代码就会抛出错误终止,所以后面的return cinema里cinema肯定是Cinema类型。
但改成用throwIf(!cinema, ...)之后,TypeScript不知道这个函数会终止代码,所以它觉得cinema还是可能是undefined,就报了类型错误:
Type 'Cinema | undefined' is not assignable to type 'Cinema'. Type 'undefined' is not assignable to type 'Cinema'.
那怎么解决呢?推荐下面这个最优雅的方案:
修改throwIf函数,让它真正抛出错误并添加断言签名
把throwIf改成实际抛出错误,同时用TypeScript的asserts断言签名告诉编译器:当这个函数正常返回时,传入的条件一定是false。这样TypeScript就能正确做类型收窄了。
修改后的完整代码:
// 用asserts签名告诉TS:函数正常返回时,condition必然为false function throwIf(condition: boolean, message: string): asserts condition is false { if (condition) { throw new Error(message); // 这里实际抛出错误,终止代码执行 } } function getCinema(cineId: number, database: Database): Cinema { throwIf(cineId == null, 'cineId should not be null') throwIf(database == null, 'database should not be null') let cinema = database.find(cinema => cinema.id === cineId) throwIf(!cinema, `No cinema with id : ${cineId}`) return cinema // 现在TS确定cinema一定是Cinema类型,不会有undefined的情况 }
为什么这个方案管用?
- 断言签名
asserts condition is false相当于给TypeScript发了个「通知」:只要这个函数没抛出错误,那传入的condition肯定是false。 - 当你调用
throwIf(!cinema, ...)后,TypeScript就会推断出!cinema是false,也就是cinema一定是有效的Cinema对象,自然就不会再报类型不匹配的错误了。 - 同时这个修改还修复了原来的逻辑漏洞——现在只要条件满足,就会直接抛出错误,不会让错误的状态继续往下执行。
如果你实在不想改throwIf的实现(非常不推荐,因为逻辑有问题),也可以手动处理返回的Error:
function throwIf(condition: boolean, message: string){ if (condition) return new Error(message) } function getCinema(cineId: number, database: Database): Cinema { const err = throwIf(cineId == null, 'cineId should not be null') if (err) throw err const errDb = throwIf(database == null, 'database should not be null') if (errDb) throw errDb let cinema = database.find(cinema => cinema.id === cineId) const errCinema = throwIf(!cinema, `No cinema with id : ${cineId}`) if (errCinema) throw errCinema return cinema }
但这个写法不仅繁琐,还很容易忘记抛出错误,导致潜在的bug,所以还是优先推荐第一种方案。
内容的提问来源于stack exchange,提问作者nekashokoi

