为什么没有严格检查承诺回调结果类型?



在Typescript中,我有一个接口MyInterface定义如下:

interface MyInterface {
hello: string
}

使用它来定义对象不允许我包含接口未描述的属性。下面会产生一个错误,指示不允许属性what,因为它未在MyInterface中定义:

const testObject: MyInterface = {
hello: 'world',
what: 'is going on',
^^^^^^^^^^^^^^^^^^^
}

但是,将其用作 promise 结果类型允许我返回未在MyInterface中定义的属性;以下内容不会产生错误:

const testPromise: Promise<MyInterface> = Promise.resolve({
hello: 'world',
what: 'is going on',
});

期望在第二个代码段中发生相同的错误是否合理?如果没有,有没有办法像对象赋值示例中那样使承诺的返回类型严格?

你所说的"严格"通常被称为"精确类型"。精确对象类型将仅接受其定义中提到的特定属性,而非精确或"开放"类型将接受额外的属性。 在 TypeScript 中,类型通常被视为开放且不精确。 这对于子类型和扩展接口非常有用:

interface Foo {
a: string
}
interface Bar extends Foo {
b: number
}
const bar: Bar = {a: "hey", b: 123};
const foo: Foo = bar; // okay

如果Foo是精确的,那么Bar就不是Foo的有效扩展,bar也不是Foo的有效实例,并且几乎整个接口/类子类型/扩展/继承功能都会被破坏。


但是发现这种开放类型并没有捕获常见的错误类,人们会拼错可选属性的键。 如果属性是可选的,则可以将其省略,如果对象类型是打开的,则拼写错误的键只是一些额外的属性。 为了解决这个问题,该语言还进行了多余的财产检查,在非常有限的情况下,某些类型被视为精确。

具体来说,仅当您尝试在需要具体对象类型的位置使用全新的("新鲜")对象文本时,才会发生这种情况。 因此,如您所见,以下内容将是一个错误:

const excessError: Foo = { a: "hey", b: 123 }; // error!
//  Object literal may only specify known properties, and 'b' does not exist in type 'Foo'.

如果在没有预期类型的地方使用对象文本,则会接受它:

const noTypeExpected = { a: "hey", b: 123 }; // no error

如果您重用不再"新鲜"的现有对象文本类型,它将被接受:

const notBrandNew: Foo = noTypeExpected; // no error

如果您使用通过泛型函数传递对象文字,则返回的类型将不再被视为"新鲜"类型:

const identity = <T>(x: T) => x;
const alsoNotBrandNew: Foo = identity({ a: "hey", b: 123 }); // no error

所以现在让我们看看承诺问题:

const promiseFoo: Promise<Foo> = Promise.resolve({ a: "hey", b: 123 }); // okay

这是有效的Promise.resolve(value)因为它是一个具有以下签名的泛型函数:

interface PromiseConstructor {
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
}

类型参数T是从value的类型推断出来的,{a: string, b: number}。所以返回类型是Promise<{a: string, b: number}>. 这可以分配给Promise<{a: string}>(因为Promise在其类型参数中是协变的,除非提示,否则我不会在这里讨论),因为该{a: string, b: number}类型不再被视为新的对象文字类型。


那么,我们如何才能从Promise.resolve()中得到"精确"的行为呢? 我能想到的最简单的方法是手动将泛型类型参数指定为Foo,而不是从value的类型推断出来:

const promiseExact = Promise.resolve<Foo>({ a: "hey", b: 123 }); // error!
// Object literal may only specify known properties, 
// and 'b' does not exist in type 'Foo | PromiseLike<Foo>'.

现在发生错误是因为Promise.resolve<Foo>(value)期望value是一个Foo | PromiseLike<Foo>,并且新对象文字被仔细检查,就好像它是精确的类型一样。


好的,希望有帮助;祝你好运!

链接到代码

似乎TypeScript将允许你写这个,但是,如果你写

testPromise.then(value => value.what)

然后,编译器将在此步骤中抱怨。

最新更新