Home > Enterprise >  Declaring a higher order function in Typescript with optional argument
Declaring a higher order function in Typescript with optional argument

Time:07-01

I am trying to declare the type of a higher order function that can take at most 1 argument.

I should be able to supply a function to my class that takes either 0 or 1 argument. My current try is this:

type FunctionWithOptionalStringArg = ((x?: string) => void)

class Logger {
    logger: FunctionWithOptionalStringArg
    constructor(logger: FunctionWithOptionalStringArg){
        this.logger = logger;
    }

    log = (x?: string) => {
        this.logger(x)
    }

}

const logString = (x: string) => console.log(x)
const logDate = () => console.log(Date.now());

const stringLogger = new Logger(logString) //Error
const dateLogger = new Logger(logDate)

However, declaring the stringLogger returns an error because my class doesn't accept a function that always takes a string argument.

How should I declare my type?

CodePudding user response:

You really don't want to make the x parameter optional. Inside your Logger class you are going to pass a string argument into the logger callback.

What you're trying to say is that the logger callback should feel free to ignore that string parameter if it wants. And this is automatically supported. So, instead of making the parameter optional, just declare that the function accepts a single string parameter. A zero-arg function is considered compatible with that also, because it is (usually) safe to call a function with extra arguments because such a function will just ignore them.

See the FAQ entry on extra function arguments for more information.

So that means you should change your code to something like:

type FunctionAcceptingStringArg = ((x: string) => void)

class Logger {
  logger: FunctionAcceptingStringArg
  constructor(logger: FunctionAcceptingStringArg) {
    this.logger = logger;
  }

  log = (x: string) => {
    this.logger(x)
  }

}

I also changed log to require a string argument. Bad things can happen if you actually call logger() with undefined for its argument, since logger might be (x: string) => console.log(x.toUpperCase()) which would give a runtime error in such cases. It is far more likely that you will always call logger with a string argument than it is for you to want an optional argument.


Now you can verify that it works as desired:

const logString = (x: string) => console.log(x.toUpperCase())
const logDate = () => console.log(Date.now());
const logStuff = (x: string, y: number) => console.log(x.toUpperCase()   y.toFixed(2))

const stringLogger = new Logger(logString) // okay
const dateLogger = new Logger(logDate) // okay
const nope = new Logger(logStuff); // error!  
// -------------------> ~~~~~~~~
// Argument of type '(x: string, y: number) => void' is not assignable to
// parameter of type 'FunctionAcceptingStringArg'

The logString and logDate callbacks are accepted, since each of them are safe to call with just a string argument. But logStuff is rejected because it requires a second argument that will not be passed in.

Playground link to code

  • Related