如何在typescript中键入异步可能的进程退出函数?



如何输入有时退出进程的异步函数?
我希望能够像这样使用它:

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 });

resultResult | 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> {
// ...
}

参见条件类型

最新更新