简体   繁体   English

打字稿,需要两个功能签名之一

[英]Typescript, require either of two function signatures

I'm working with a library that gives me aa callback, which expects either an error as it's first argument, or null as an error and a value as it's second argument. 我与给我AA回调,其预计无论是作为它的第一个参数,或null作为一个错误,一个值作为它的第二个参数的误差图书馆工作。

I'm trying to encode this with a type, so typescript will validate that I'm for example not passing in both a error and a value, or neither of them. 我正在尝试使用一种类型对此进行编码,因此打字稿将验证我是否未同时传递错误和值,或者两者均未传递。

 type Callback = { (error: undefined, value: string): void; (error: Error): void; } function doThings(c: Callback) { // Valid, no error, and a useful value. c(undefined, '1'); // Valid, something went wrong, so we pass in an error, but no value. c(new Error()); // Invalid. Since there's no value, there must be an error. c(undefined); // Invalid, there's both an error and a value. c(new Error(), '1'); } function c1(error: undefined, value: string) { } function c2(error: Error) { } doThings(c1) doThings(c2) 

You can use a combination of sum and intersection types to achieve this: 您可以使用求和和交集类型的组合来实现此目的:

type ValueCallback = (error: undefined, value: string) => void;
type ErrorCallback = (error: Error) => void;
type Callback = (ValueCallback | ErrorCallback) & ((error: Error | undefined, value?: string) => void);

function doThings(c: Callback) {
  c(undefined, "1");
  c(new Error());
}

function c1(error: undefined, value: string) {}
function c2(error: Error) {}

doThings(c1);
doThings(c2);

The intersection part is needed so you can actually call the function inside doThings , without that it doesn't find a compatible call signature. 需要交集部分,因此您实际上可以在doThings内部调用该函数,否则它将找不到兼容的调用签名。

As far as I can tell, your definition of Callback is exactly as you want it to be. 据我所知,您对Callback的定义完全符合您的要求。 Callback is a function that can be called with arguments (error: undefined, value: string) . Callback是可以使用参数(error: undefined, value: string)调用的函数。 It is also a function that can be called with arguments (error: Error) . 也是一个可以使用参数调用的函数(error: Error) A valid Callback must support being called in both of those ways . 有效的Callback 必须支持通过这两种方式进行调用

To reiterate, this is just fine: 重申一下,这很好:

type Callback = {
  (error: undefined, value: string): void;
  (error: Error): void;
}

function doThings(c: Callback) {
    c(undefined, '1');  // okay
    c(new Error()); // okay
}

And the TypeScript compiler doesn't report any errors there, which is good. 而且TypeScript编译器在那里不报告任何错误,这很好。


The part that seems to be a problem is that you then go ahead and define two functions, neither of which conforms to the Callback specification. 看起来有问题的部分是您接着继续定义两个函数, 两个函数都不符合Callback规范。 Let's look at them. 让我们看看它们。 First: 第一:

function c1(error: undefined, value: string) { 
   // some impl which expects value to be a string, e.g.,
   value.charAt(0);
};
doThings(c1); // error, as expected

This function requires two arguments. 此函数需要两个参数。 You cannot call it with arguments (error: Error) . 您不能使用参数调用它(error: Error) So the call doThings(c1) is an error by the compiler, which is exactly what you want. 因此,调用doThings(c1)是编译器的错误,这正是您想要的。 The compiler error tells you that c1 is not a Callback . 编译器错误告诉您c1不是Callback If you force the compiler to allow it with type assertions or other trickery, everything will be fine at compiler time, and then at runtime doThings(c1) will end up calling c1(new Error()) , which, assuming that c1 does string-like stuff with its value argument (as I showed above), ends up throwing an error like Error: undefined has no properties. 如果您强制编译器允许使用类型断言或其他doThings(c1) ,则在编译时一切都会好起来,然后在运行时doThings(c1)最终将调用c1(new Error()) ,假设c1确实是字符串value参数(如我上面所示)的类东西,最终会引发错误,如Error: undefined has no properties.

Similarly: 同理:

function c2(error: Error) { 
  // some impl which expects error to be an Error, e.g.,
    console.log(error.message);
 };
 doThings(c2); // error, as expected

This function requires that the first argument be an Error . 此函数要求第一个参数为Error You cannot call it with arguments (error: undefined, value: string) . 您不能使用参数(error: undefined, value: string)来调用它。 So the call doThings(c2) is an error by the compiler, which is exactly what you want. 因此,调用doThings(c2)是编译器的错误,这正是您想要的。 The compiler error tells you that c2 is not a Callback . 编译器错误告诉您c2不是Callback If you force the compiler to allow it with type assertions or other trickery, everything will be fine at compiler time, and then at runtime doThings(c2) will end up calling c2(undefined, '1') , which, assuming that c2 does Error-like stuff with its error argument (as I showed above), ends up throwing an error like Error: undefined has no properties. 如果您强制编译器允许使用类型断言或其他doThings(c2) ,那么一切都会在编译时很好,然后在运行时doThings(c2)最终将调用c2(undefined, '1') ,假设c2确实带有error参数的类似错误的东西(如我在上面所示),最终抛出诸如Error: undefined has no properties.


So neither c1 nor c2 are valid Callback objects. 因此, c1c2都不是有效的Callback对象。 If you want to make a valid Callback , you can do it. 如果您想进行有效的Callback ,则可以执行。 One way is to make a more specific function type, like so: 一种方法是制作更具体的函数类型,如下所示:

function cSubtype(error: undefined | Error, value?: string) {
  if ((typeof error === 'undefined') && (typeof value === 'string')) {
    c1(error, value);
  } else if ((typeof error !== 'undefined') && (typeof value === 'undefined')) {
    c2(error);
  } else console.log('I got some other arguments');
}
doThings(cSubtype); // okay

The cSubtype function is more specific than a Callback , in that it accepts more general parameters. cSubtype函数比Callback更为具体,因为它接受更多常规参数。 (Function parameters vary contravariantly , meaning that the more general/wider you make function parameters, the more specific/narrower you make the function type.) It accepts arguments like (error: undefined, value: string) as well as (error: Error) . (功能参数而变化contravariantly ,这意味着更多的普通/更宽你做出函数的参数,更具体的/窄你做出函数类型。)它接受等参数(error: undefined, value: string)以及(error: Error) It also accepts (error: Error, value: string) and (error: undefined) . 它还接受(error: Error, value: string)(error: undefined) But you can still pass cWider to doThings , because cWider is a subtype of Callback , just like a {a: string, b: boolean} can be passed to something expecting a {a: string} . 但是您仍然可以将cWider传递给doThings ,因为cWiderCallback子类型 ,就像{a: string, b: boolean}可以传递给期望{a: string}

Another way you can do this is just to define an overloaded function which behaves exactly as you want a Callback to behave. 另一种执行此操作的方法是定义一个重载函数,该函数的行为与您希望Callback行为完全相同。 It still needs an implementation signature which is more general, but it cannot be called with the implementation signature (read the link about overloads for more info). 它仍然需要一个更通用的实现签名,但是不能与实现签名一起调用(有关更多信息,请参见有关重载的链接)。 Here's an example: 这是一个例子:

function cOverload(error: undefined, value: string): void; // signature1
function cOverload(error: Error): void; // signature2
function cOverload(error: undefined | Error, value?: string) { //impl 
  if (typeof value !== 'undefined') {
    c1(undefined, value);
  } else {
    c2(error!);
  }
}
doThings(cOverload);  // works

Does that make sense? 那有意义吗? Hope it helps. 希望能帮助到你。 Good luck. 祝好运。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM