Issue
I have an in-memory cache function which you can pass a function and its arguments to that will cache the results. The function itself works but I can't figure out how create the generic type signature so that I get errors if I call the cache with a function but not the arguments that the function requires. Here is a minimal example:
type AnyFunc = (...args: any[]) => any;
type MemCache<T extends AnyFunc> = {
(fn: T, ...args: Parameters<T>): ReturnType<T>;
debug: () => void;
};
function makeCache<T extends AnyFunc>(): MemCache<T> {
const cache = {};
function queryCache<T extends AnyFunc>(fn: T, ...args: Parameters<T>): ReturnType<T> {
return fn(...args)
}
queryCache.debug = () => {
console.log(cache)
};
return queryCache;
}
const memcache = makeCache()
const fn = (num: number, str: string) => 'hello'
//@ts-expect-error
const res = memcache(fn) // should error because 3 arguments are expected but received 1
//@ts-expect-error
res.then() // should error because fn returns a string, not a promise
You can also see the example in the typescript playground.
I have tried extracting the inner function (queryCache
) and calling that separately and the compiler errors as expected. The issue appears to be the outer function but I need that because that gets called when the module loads to instantiate the cache which the queryCache
function closes over.
Solution
The main problem here is that the generic type parameter T
in MemCache
is inappropriately scoped. You've applied it at the top level, to the whole MemCache
type, whereas it really belongs only on the call signature:
type MemCache = {
<T extends AnyFunc>(fn: T, ...args: Parameters<T>): ReturnType<T>;
debug: () => void;
};
See typescript difference between placement of generics arguments for a detailed description about why such things make a difference. In short, you want a MemCache
to be able to handle any T
that the caller wants, not just a particular one chosen by the implementer of MemCache
. Anyway, now makeCache()
looks like
function makeCache(): MemCache {
const cache = {};
function queryCache<T extends AnyFunc>(
fn: T, ...args: Parameters<T>): ReturnType<T> {
return fn(...args)
}
queryCache.debug = () => {
console.log(cache)
};
return queryCache;
}
And when you call it you get the behavior you expect:
const memcache = makeCache()
const fn = (num: number, str: string) => 'hello'
const res = memcache(fn) // err!
// Expected 3 arguments, but got 1
res.then() // err!
// Property 'then' does not exist on type 'string'.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.