Background:
Callback functions are a common practice in JS and TS for asynchronous programming and delegating responsibility. In TypeScript, typing such functions is an important part of ensuring safety, especially when parameters can be optional or have default values.
Problem:
In dynamic JS, the lack of typing for callback arguments can lead to value passing errors, confusion with undefined, and incorrect parameter order. In TypeScript, typing is required to avoid such issues, but it can be challenging to maintain all nuances related to optionality, order, and default values from the outside.
Solution:
Explicitly specify the types of all parameters, mark optional parameters with a question mark, and specify default values directly in the function declaration while also ensuring they are correctly described in the type.
Example code:
function fetchData( url: string, callback: (data: any, error?: Error) => void ) { // ... } // Callback with an optional error parameter fetchData('/api', (data, error) => { if (error) { // handling } else { // success } }); // Callback with a default parameter function process( cb: (x: number, y?: number) => void = (x, y = 10) => { /* ... */ } ) { /* ... */ }
Key points:
Is the consumer of the callback required to explicitly consider all parameters, including optional ones?
No, they can skip optional parameters, and TypeScript won't throw an error — the handling works correctly due to the question mark syntax.
Can the first parameter in the callback be optional while the second is mandatory?
No. Optional parameters must always be at the end of the argument list. Violating this order will lead to a typing error.
What happens if the parameter's optionality is not specified but it's not passed at the call site?
TypeScript will throw an error — if the parameter is mandatory, it must be passed. Only optional or default ones can be omitted.
Made the second argument of the callback mandatory but did not provide it in the call. Received a compilation error.
Pros:
Cons:
Typed the callback with an optional parameter:
(cb: (x: number, y?: number) => void)
Or set a default:
f = (x: number, y = 10) => { ... }
Pros:
Cons: