It's important to note that in TypeScript, a function with fewer trailing parameters can be substituted for a function with more trailing parameters as long as the parameter types match. This concept is explicitly addressed in the TypeScript FAQ due to its common occurrence.
The rationale behind this is that any function in JavaScript can be called with any number of parameters, and it is always safe for a caller to send more parameters than expected to a function - the extra parameters are simply ignored. For example:
const aCallback: CallbackWithNameParameter = {
cb: () => {}
};
This code snippet is valid because the aCallback.cb
function ignores the first parameter (which must be a string). However, the following case with aSecondCallback
would be invalid as it attempts to interpret the first parameter as a number instead of a string, potentially leading to errors.
That pretty much covers the basics. Good luck!
Update 1
In response to an inquiry by @jbmilgrom, about why aCallback.cb()
results in a compile error although its definition seems correct, the explanation lies in the declaration of the type
CallbackWithNameParameter</code for <code>aCallback
. Essentially, TypeScript enforces type constraints based on the declared types, not on actual values, which causes the compile error in this scenario.
A workaround would involve defining a separate interface like so:
interface CallbackWithNoParameter {
cb: () => void
}
const aCallback: CallbackWithNoParameter = {
cb: () => {}
};
aCallback.cb(); // this works fine
Regarding substitutability, while you can assign a narrower type to a wider type without issue, attempting the reverse generally results in a compilation error.
Update 2
@jbmilgrom further questioned why TypeScript allows defining something that can never be invoked as defined, pointing out inconsistencies in how substitutable types are handled. The analogy used here likens pouring milk into a container labeled "liquid" (wider type), then expecting it to act like milk when poured into tea, highlighting the confusion caused by type declarations.
To better handle such scenarios, creating explicit type differentiations rather than relying solely on broader types is recommended. Checking if a function accepts zero arguments without causing runtime issues can be challenging, as shown in the provided code snippet.