Reputation: 3093
I'm trying to create a higher order function that can attach a primitive watcher to a method. It's working great as long as the input function is not overloaded, but I'm running into issues with overloads. I would like to find a way to pass all overloads through the higher order function so the returned function can behave just like the original. This is what I have so far:
I build a watcher with a function like this, giving me a simple way to track statistics of function use.
function makeWatcher() {
let _callCount = 0;
function countCall() {
_callCount = _callCount + 1;
}
function getCallCount() {
return _callCount;
}
return {
countCall,
getCallCount,
};
}
I then have a higher order function that takes a function and a watcher as input, and returns a function that interacts with the watcher and then runs the original function.
// Generic type assures method signature is inferred.
function watchMethod<T extends (...args: any[]) => any>(
func: T,
watcher: Watcher
): (...funcArgs: Parameters<T>) => ReturnType<T> {
// Return a new function that has same signature as input function,
// with added watcher interaction when called
return (...args: Parameters<T>): ReturnType<T> => {
// Interact with watcher
watcher.countCall();
// execute original function and return results
const results = func(...args);
return results;
};
}
The last step is to compose these together to give me a method that has properties I can interact with, like so:
function buildWatchedMethod<T extends (...args: any[]) => any>(inputFunction: T) {
// get a watcher
const watcher = makeWatcher();
// set up method watching
const watchedMethod= watchMethod(inputFunction, watcher);
// list out properties that should be public
const externalProperties = {
getCallCount: watcher.getCallCount
}
// assign properties to method
return Object.assign(watchedMethod, externalProperties);
}
I can then add watchers to methods like this:
function method(input: string){
console.log(input);
}
const watchedMethod = buildWatchedMethod(method);
watchedMethod("Hello World"); // logs "Hello World"
watchedMethod.getCallCount(); // returns 1 - method was called once
watchedMethod("Hello again"); // logs "Hello Again"
watchedMethod.getCallCount(); // returns 2
watchedMethod.countCall(); // error - not available from outside function
This works beautifully, function signatures are preserved, type safety is preserved - until buildWatchedMethod
is called with an overloaded method. It still works, but the overloads of the watched methods aren't preserved through the process, which makes typescript complain.
This appears to be expected behavior as outlined in the accepted answer of this post, but is there any way to carry the overload signatures through the higher order function?
Where the issue comes up most often is when I use libraries where overloads are common. (I'm looking at you, Mongoose!) I'm also trying to avoid mocking libraries like Sinon because a) they can be painful to use with typescript sometimes, and b) my needs for this use case are very minimal, and third party libraries tend to have a lot more involved than what I need.
Any suggestions are appreciated!
Upvotes: 0
Views: 285
Reputation: 11696
I believe there's a way to make this work.
The problem is the watchMethod
return type... representing the original function type using Parameters<T>
doesn't appear to capture the overloads (since Parameters
only returns a single parameter list). However, if we return T
directly, the overloads can make it through. The only catch is that Typescript will complain about the return type, so you're going to have to use a type assertion. If you're willing to do that, then the following modification should solve your problem:
function watchMethod<T extends (...args: any[]) => any>(
func: T,
watcher: Watcher
): T {
// Return a new function that has same signature as input function,
// with added watcher interaction when called
return ((...args: Parameters<T>): ReturnType<T> => {
// Interact with watcher
watcher.countCall();
// execute original function and return results
const results = func(...args);
return results;
}) as T;
}
Upvotes: 1