Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional leading parameters in TypeScript

While converting a JavaScript library protocol into TypeScript, I stumbled upon use of leading optional parameters, as opposed to regular / trailing ones.

A method in JavaScript:

db.task(function (context) {
    // executing task
});

has an optional name for the task that can be injected in front:

db.task('myTaskName', function (context) {
    // executing task
});

This is done to make the code more readable, having the task name up in front, as opposed to somewhere in the end, which would look wrong / unintuitive.

How does one code around such parameters in TypeScript?

I know I can declare both parameters as optional, but this wouldn't be the case, because the callback function is required as either the first or the second parameter. And if it makes this any simpler, we could say - the last parameter must be a callback function.

like image 748
vitaly-t Avatar asked Mar 28 '16 14:03

vitaly-t


People also ask

How do you pass optional parameters in TypeScript?

In Typescript, making optional parameters is done by appending the “?” at the end of the parameter name in the function when declaring the parameters and the parameters which are not marked with “?” i.e not optional parameter are called as default parameters or normal parameters where it is must and compulsory to pass ...

What is optional parameter function in TypeScript?

TypeScript provides a Optional parameters feature. By using Optional parameters featuers, we can declare some paramters in the function optional, so that client need not required to pass value to optional parameters.

Why are optional parameters added in TypeScript?

Developers can use the optional parameter to declare parameters in function optional so that the requirement to pass the value to optional parameters gets eliminated.

Does TypeScript have optionals?

You must tell TypeScript if a property is optional. First, if you don't tell TypeScript that a property is optional, it will expect it to be set. Adding ? to the property name on a type, interface, or class definition will mark that property as optional.


2 Answers

The accepted answer uses a few hacky things:

  • Type Assertion : Do not use this unless you have to. It is dangerous : https://basarat.gitbooks.io/typescript/content/docs/types/type-assertion.html Instead one should use a type guard : https://basarat.gitbooks.io/typescript/content/docs/types/typeGuard.html
  • Leaves the method open for erroneous calls (e.g. myDefinedMethod('test') i.e. no callback is provided). Instead one should use function overloading : https://basarat.gitbooks.io/typescript/content/docs/types/functions.html

The right way to do this

Here is an example:

type Cb = (context: any) => any;
function task(cb: Cb);
function task(name: string, cb: Cb);
function task(nameOrCb: string | Cb, cb?: Cb) {
    if (typeof nameOrCb === 'string') {
        const name = nameOrCb; // You can see that `name` has the inferred type `string`
        // do something
    }
    else {
        const cb = nameOrCb; // You can see that `cb` has inferred type `Cb`
        // do something
    }
}

// Tests
task((a) => null); // Ok
task('test', (a) => null) // Ok

// Type Safety
task((a, b) => null); // Error: function does not match type cb
task('test'); // Error: `cb` must be provided for this overload
like image 165
basarat Avatar answered Oct 20 '22 18:10

basarat


If you mean when you are defining parameters in your function you would mark them with the ? symbol for optional and use the | to show the various types that could be injected. In the function itself you have to see what was passed in to which function. RequireJS does this in their define functions and they have a .d.ts (definitely typed) file that shows this.

Example with a method/function

// definition
function myDefinedMethod(callback: (someVar:any)=>any):void;
function myDefinedMethod(name: string, callBack: (someVar: any) => any): void;
function myDefinedMethod(nameOrCallback: string | ((someVar:any)=>any), callBack ?: (someVar: any) => any): void {
    var name = "";
    if (typeof nameOrCallback === 'string') {
        name = nameOrCallback;
    }
    else {
        callBack = nameOrCallback;
    }

    // both name and callback are now defined although name can be empty
    // do something
    console.log(name);
}
  • This approach uses method overloading to ensure type safety, this will prevent a caller from calling the method with only a string at transpile time (as typescript is not compiled).
  • In the 3rd function definition the parameter nameOrCallback could be either the function or the name of the parameter. If it is a string then the callback cannot be undefined.

Edit

Thank you @basarat, I have updated my answer based on your feedback. This is indeed a better structure as you are ensuring the caller cannot execute your method without supplying the expected mandatory parameters like the callback. I did use Function before but only as a placeholder for whatever function definition that the OP would want to use and was not intended as a final type parameter. To clarify this I have updated the code with an inline callback definition like yours. Again, thank you for your input. This does indeed make the code better by ensuring type safety and ensuring that a caller can only call the method as intended.

like image 7
Igor Avatar answered Oct 20 '22 19:10

Igor