为什么在可达性分析中不考虑Promise <never>?

nik*_*eee 5 type-inference reachability promise async-await typescript

假设我们有这个功能:

function returnNever(): never {
    throw new Error();
}
Run Code Online (Sandbox Code Playgroud)

创建IIF时,其后的代码将标记为不可访问:

(async () => {
    let b: string;
    let a0 = returnNever();
    b = ""; // Unreachable
    b.toUpperCase(); // Unreachable
})();
Run Code Online (Sandbox Code Playgroud)

这按预期工作。请注意,a0推断类型为never

但是,如果returnNever()返回a Promise<never>并等待,则行为是不同的:

(async () => {
    let b: string;
    let a1 = await Promise.reject(); // returns Promise<never>
    b = ""; // Not unreachable?
    b.toUpperCase(); // Not unreachable?
})();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,a1也被推断为type never。但是之后的代码并未标记为无法访问。为什么?

背景:最近我偶然发现了一些logError类似于以下代码的函数。它在一个catch块内使用。通过这种方式,我发现,可及性分析不仅受到可及性分析的影响,而且还受到明确分配分析的影响:

declare function fetchB(): Promise<string>;
async function logError(err: any): Promise<never> {
    await fetch("/foo/...");
    throw new Error(err);
}
(async () => {
    let b: string;
    try {
        b = await fetchB(); // Promise<string>
    } catch (err) {
        await logError(err); // awaiting Promise<never>
    }
    b.toUpperCase(); // Error: "b" is used before assignment
})();
Run Code Online (Sandbox Code Playgroud)

如果logError是同步的(通过删除所有await与和有关asynclogError),则没有错误。另外,如果let b: string将其更改为let b: string | undefinedundefined则在try-catch块之后不会将其删除。

它似乎有一个理由不考虑awaitPromise<never>-returning功能的控制流分析的任何方面。它也可能是一个错误,但我认为我在这里缺少一些细节。

Atu*_*put -1

当您在 TypeScript 中等待 A 时, A**Promise<never>**被视为可能引发任何错误(包括运行时错误)的类型。因为运行时有可能抛出错误,所以await之后的代码被认为是可达的。

为了为意外的运行时错误做好准备,TypeScript 的Promise<never>类型行为旨在更加自由和保守。尽管有时不合逻辑,但这是一个经过深思熟虑的选择,旨在解决承诺和潜在的运行时异常带来的不确定性,同时保持类型系统的健全性。

  • “*因为在运行时有可能抛出错误,所以等待之后的代码被认为是可达的。*” - 这是没有意义的。重点是,“机会”是 100%,实际上没有机会不会引发异常,因此 `await` 之后的代码应该被视为无法访问。 (3认同)