TypeScript 编译器无法处理promise.then(null | undefined)

eqzww0vc  于 2个月前  发布在  TypeScript
关注(0)|答案(8)|浏览(40)

🔎 搜索关键词

"promise", "then", "null", "undefined", "nullable"

🕗 版本与回归信息

  • 在版本3.33.3333和3.5.1之间发生了变化

⏯ Playground链接

https://www.typescriptlang.org/play/?ts=5.4.5#code/PTAEBkFMBdQQ1ABwE4HsC2BLAzpU0ALOWAd0wBtzRlJtVyA3PaVUABgG4AoAY1QDtssRKAC8oAAposuAHQ06jSAAo2ASg6guXEKAAqBHKCN9kNHtAA0oPukQVIyY-wBmj7JOk5IAHn4BXdAAjRwA+Lhd-fgtMASQ2ZTVQAG8uUHTqGH9kfiRuAF8dMAMjIzhyOhtUM0gLa1t7ckdQJmRMF0xaTwxvP0CQ5FCq-mhkOAsIqJi4xABGRIAubplfAOCwlLSMmmhs3MQCov1DDzKK1lNzaCWGhydIV2qeLqke3B8hNv4AcyG+EbGFi0uj0AE9EHgAORrAaQ4wefioWBwbDYTDffhwIJNfCsaDgqGfTA-SGySbRaCxfYAJkWy16RJ+Q1SGUyuxyeS4hRBJ1A2AIqH85AAJqBHGgnEF-LBbk0nNg4KDTrAjB1+JBydN9gBmOmvFYfUbE36bVk7PZIWSEB7KAKUDRco4ASRscFyQRRmB45XIoJs5DgmHQ+CIsEIqqmlLi5pyHjdoPDP01Uf2ABY9V53gB1YnC1AkUAAH1AABFiHhiwBVABK4CLKXg-AThh+SwARDQfaC26B8sytukY-srQQbXbyA7ucVefzBSL4OcxWZqqApTKMI1mgqlcYVR41RrIhSqUgAKwZt6+RkmlnbLIcxAjm1RYWQA-CydAA

💻 代码

// Your code here

🙁 实际行为

// Let a promise that will resolve to 0;
const p = Promise.resolve(0); 

// This should error but compiler says it is fine
function p3(): Promise<string> {
    return p.then(null);
}
// I can basically claim that this function returns anything
function p4(): Promise<Window | Date | URL | { anything: "really" }> {
    return p.then(null);
}
// This should also error but compiler says it is fine
function p5(): Promise<string> {
    return p.then(undefined);
}

🙂 预期行为

// Let a promise that will resolve to 0;
const p = Promise.resolve(0); 

// This should error with: 
// Type 'number' is not assignable to type 'string'.
function p3(): Promise<string> {
    return p.then(null);
}
// This should error with:
// Type 'number' is not assignable to type 'string'.
function p5(): Promise<string> {
    return p.then(undefined);
}

关于问题的附加信息

  • Playground版本3.33.3333
  • Playground版本3.5.1
vecaoik1

vecaoik11#

这是正确的行为 - 在运行时,.then(null) 使用原始底层值创建一个新的 Promise

var a = Promise.resolve("hello world").then(null).then(x => console.log(x))

// prints 'hello world'

虽然你可能更喜欢永远不允许将 null | undefined 传递给 .then() ,但它既被规范允许,也允许了可以说是有用的模式(例如可选的处理器来转换值)。

6yt4nkrj

6yt4nkrj2#

我认为这里展示的问题不是这个。最小重现:

const pNumber = Promise.resolve(0);
const pString: Promise<string> = pNumber.then(); // No error

问题在于,如果没有为 then 提供回调参数,那么它类型参数的唯一推断来源是上下文类型(在我的类型注解中,以及来自 @blackdyedsheep 的重现中的函数返回类型)。

dwbf0jvd

dwbf0jvd3#

哎呀,我读错了。抱歉。

fd3cxomn

fd3cxomn4#

我想到的简单解决方法是添加一个重载函数,用于处理可选的 undefined / null ,但我不确定这与最简单的解决方法相比如何。“只是不要写 .then(null) ”。有这样的实际用例吗?

pxy2qtax

pxy2qtax5#

是的,确实有一个实际的使用案例,这就是我遇到这种行为的原因。
引用@DanielRosenwasser:
可选的处理器来转换值
例如这段代码应该推断出 Promise<0 | string> ,但却推断出了 Promise<string>

function maybeTransform(fn?: (v: number) => string) {
	return Promise.resolve(0).then(fn);
}

而这段代码不应该编译

function maybeTransform(fn?: (v: number) => string): Promise<string> {
	return Promise.resolve(0).then(fn);
}

基本上,它应该表现得和

function maybeTransform(fn?: (v: number) => string) {
	return fn ? fn(0) : 0;
}

一样。

cbjzeqam

cbjzeqam6#

我认为可以通过重载来解决这个问题,但由于重载解析不会在推断过程中发生,我认为它将变得非常不稳定。#58678 取决于实验,但我不认为它是可行的。

nnt7mjpx

nnt7mjpx7#

可能没有人会调用 Promise.resolve(0).then<string>()except when they do 。我认为,无论是从意料之外的地方推断类型,还是手动指定类型参数,基本问题都是相同的:泛型默认值并不完全符合使用场景,这看起来像 #56315 。有问题的使用场景类似于“当调用者省略可选事物时,实际上是由实现者提供的”。泛型默认值在某种程度上做到了这一点,但并非总是如此。

ncecgwcz

ncecgwcz8#

我认为这个问题可以通过重载来解决,但由于重载解析不会在推断过程中发生,我认为它将变得非常不稳定。#58678 取决于实验,但我认为它不会可行。

interface Promise<T> {
  // specific overloads
  then<TResult1, TResult2>(
    onfulfilled: (value: T) => TResult1 | PromiseLike<TResult1>,
    onrejected: (reason: any) => TResult2 | PromiseLike<TResult2>
  ): Promise<TResult1 | TResult2>;
  then<TResult>(onfulfilled: null | undefined, onrejected: (reason: any) => TResult | PromiseLike<TResult>): Promise<T | TResult>;
  then<TResult>(onfulfilled: (value: T) => TResult | PromiseLike<TResult>, onrejected?: null | undefined): Promise<TResult>;
  then(onfulfilled?: null | undefined, onrejected?: null | undefined): Promise<T>;

  // catch all overload
  then<TResult1 = T, TResult2 = never>(
    onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
    onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
  ): Promise<TResult1 | TResult2>;
}

这相当准确,大部分情况下都能按预期工作。特定的重载彼此互斥,旨在避免在 then() 的返回类型中引入一个可能在回调为 nullundefined 或未提供时无法观察到的类型变量。捕获所有重载允许在您可能传递一个类型为 (() => T) | null | undefined 的变量的情况下,以便它不会与任何特定重载匹配,尽管这是完全合法的。

不幸的是,它导致 #36307 退化,因此它仍然不是完全可靠的。
理想情况下,我们可以通过依赖于与 TResult1 具有默认值且除了返回类型之外没有其他推断的事实相关的某种启发式方法来在推断过程中解决这个问题。不幸的是,我还没有找到一个可靠且不破坏其他预期的启发式方法。

相关问题