如何输入有时退出进程的异步函数?
我希望能够像这样使用它:
function useResult(result:Result): void {
// ...
}
const result: Result | undefined = await getResult();
if (result === undefined) reportError({ fatal: true });
useResult(result) // I want the type of result to be `Result` here, not `Result | undefined`
const result: Result | undefined = await getResult();
if (result === undefined) reportError({ fatal: false });
useResult(result) // I want the type of result to be `Result | undefined` here
下面是有问题的函数:
import { exit } from "node:process";
interface ReportableError {
fatal?: true | false | undefined;
// ...
}
export async function reportError<E extends ReportableError>(
e: E,
) { // <-- How do I type the return of this function?
// Log the error, do some async stuff...
if (e.fatal) {
// Stop running
exit();
}
// Continue running
}
我认为这将工作的唯一方法是,如果你使reportError()
一个重载函数与单独的调用签名的"fatal"one_answers";non-fatal"输入的种类
本质上,您希望reportError({fatal: true})
返回never
类型,因为该函数永远不会返回。当调用调用签名返回类型为never
的函数时,编译器可以根据代码的可达性使用控制流分析来缩小变量的类型。这是在microsoft/TypeScript#32695中实现的,只有当返回类型被显式注释为never
时才会触发。
既然你想
if (result === undefined) reportError({ fatal: true });
将result
从Result | undefined
缩小到Result
,这意味着reportError()
必须返回never
。所以reportError
的一个呼叫签名应该看起来像
function reportError(e: { fatal: true }): never;
注意,我们不能写async function reportError(e: {fatal: true}): Promise<never>
,因为Promise<never>
不影响我们想要的可达性…这在microsoft/TypeScript#34955中被标记为bug。
另一方面,当您调用reportError()
时,fatal
不是true
,您希望这只是一个返回void
的异步函数…嗯,Promise<void>
。这样的:
async function reportError(e: { fatal?: false | undefined }): Promise<void>;
我们必须将致命和非致命的情况分离到它们自己的呼叫签名中,以便可达性分析以我们想要的方式工作。返回条件类型(如<E>(e: ReportableError) => E extends {fatal: true} ? never : Promise<void>
)的单个泛型调用签名在概念上是相同的,但编译器不会将其视为断言。
好的,我们有两个调用签名,我们需要一个实现。完整的函数看起来像
function reportError(e: { fatal: true }): never;
async function reportError(e: { fatal?: false | undefined }): Promise<void>;
async function reportError(e: ReportableError) {
if (e.fatal) {
throw new Error("EXITING PROCESS OR SOMETHING");
}
}
让我们看看它是否像我们想要的那样工作:
const result: Result | undefined = await getResult();
if (result === undefined) reportError({ fatal: true });
useResult(result) // okay
很好,result
缩小了。如果我们改变死亡率:
const result: Result | undefined = await getResult();
if (result === undefined) reportError({ fatal: false });
useResult(result) // error! undefined is not assignable to Result
现在result
没有收窄,编译器不喜欢useResult(result)
,因为useResult()
不接受undefined
。
Playground链接到代码
export async function reportError<E extends ReportableError>(
e: E,
): E["fatal"] extends true ? never : Promise<Result | undefined> {
// ...
}
参见条件类型