简体   繁体   中英

Wrapping an Overloaded Typescript Function

I'm having trouble writing a wrapper for an overloaded function.

The function I'm wrapping is the SendGrid email sending function . That function is overloaded like (simplified):

declare class MailService {
  send(data: Email): foo;
  send(data: Email[]): foo;
}

I want to write a function that can either accept an Email or Email[] . But when I do it like this, it complains that no overload matches the call:

function sendEmailWrapper1(data: Email | Email[]) {
  sendGrid.send(data)
}

However, this works:

function sendEmailWrapper2(data: Email | Email[]) {
  if ('length' in data) {
    sendGrid.send(data)
  } else {
    sendGrid.send(data)
  }
}

I think that what's happening is that Email | Email[] Email | Email[] isn't a valid input for send(data: Email) or for send(data: Email[]) , and Typescript doesn't realize that the overloads mean that sendGrid.send(data) should be valid (and, indeed, if the if and else branches can have the same code and be valid, that makes it seem like Typescript should be able to realize that the statement is valid even without an if statement to help it infer a type since Typescript is compile-time only and won't actually direct the runtime code execution to make different function calls based on the types).

My two questions:

  1. What's the best practice in situations like these?
  2. Is this just a shortcoming in the Typescript compiler, or is there a good reason that sendEmailWrapper1 would fail to compile but sendEmailWrapper2 would work?

Thanks!

This is a shortcoming of the the class MailService declaration. It could have a more accurate type definition like this:

declare class MailService {
  send(data: Email | Email[]): foo;
}

The best practice, in my opinion, is to augment the type definition locally. Then submit a pull request to the repository with the change.

Also, this is a wrapper that uses rest parameters. It will work with less effort on your part:

// Accepts on or more MailData objects.
// sendEmailWrapper3(data);
// sendEmailWrapper3(data, data1);
function sendEmailWrapper3(...data: MailData[]) {
  sendGrid.send(data);
}

Actually this usage is not a good example of overloading, if you need to overload methods, it means you need to return different types when parameters' type is changed. If you need different return types then overloading methods is more consistent. For example :

send(data: Email): foo; 
send(data: Email[]): foo[]

or if parameters have an optional property but when the property is set then an another property must be required.

function bar(params:{
    prop1: number;
    prop2?: boolean;
}): Foo;
function bar(params:{
    prop1: number;
    prop2: boolean;
    prop3: string;
}): Foo;

From the TypeScript documentation on Overloads :

JavaScript is inherently a very dynamic language. It's not uncommon for a single JavaScript function to return different types of objects based on the shape of the arguments passed in.

As Cenk's answer , the typing of send() should use overloads only if the return types are different. Otherwise, send(data: Email | Email[]): foo would be more appropriate and handy for users of the API. So It's a good option for your wrapper to have this type.

To improve the wrapper body code in this case, we can convert data to Email[] to use only one overload and avoid the apparent code duplication:

  • const emails = Array.isArray(data) ? data : [data] const emails = Array.isArray(data) ? data : [data] or
  • const emails = ([] as Email[]).concat(data) then
  • sendGrid.send(emails)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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