I have a method with a function param.
export const workFunction = (fn) => {
let response = null;
const exec = async (...args) => {
response = await fn(...args);
console.log(response);
};
return {
response,
exec,
};
};
this is section of composable function of vue
in the next step i use this function like bottom.
// exportFunction is imported function
const createApi = workFunction(exportFunction);
createApi.exec();
and my problem: i can't see exportFunction type of params in createApi.exec(). tip: exportFunction is dynamic function.
CodePudding user response:
Your typing problem can be fixed by annotating the parameter types of workFunction()
. Since you want the output's type to depend on the type of the fn
parameter, you'll need to make it generic.
Here's how I'd do it:
const workFunction = <A extends any[], R>(fn: (...args: A) => R) => {
const exec = async (...args: A) => {
ret.response = await fn(...args);
console.log(ret.response);
};
const ret = {
response: null as Awaited<R> | null,
exec
}
return ret;
};
So fn
takes a rest parameter of generic type A
, and returns a value of generic type R
. It returns an object with an exec
method that also accepts a rest parameter of type A
, and a response
property of the union type Awaited<R> | null
. It's using the Awaited<R>
helper type to indicate that if R
is a Promise
type, then you're getting the resolved type of R
's promise, otherwise you're just getting R
, and it could always be null
because someone could look at response
before calling exec
.
Note that I also had to fix your implementation problem; the original version had a closed-over variable named response
that you would reassign after awaiting fn(...args)
. But reassigning that variable wouldn't modify the property named response
in the returned object. That property would always have been null
forever. If you want to reassign a property you need to do it through the containing object. So I have removed the response
variable completely and am using just the property inside the returned object. And that means the returned object does need to be closed over, so I gave it the name ret
.
Anyway, let's test it:
function exportFunction(x: string) {
return Promise.resolve(x.toUpperCase());
}
// exportFunction(x: string): Promise<string>
const createApi = workFunction(exportFunction);
/* const createApi: {
response: string | null;
exec: (x: string) => Promise<void>;
} */
Looks good. exportFunction()
is of type (x: string) => Promise<string>
, and createApi
is of type {response: string | null; exec: (x: string) => Promise<void>}
. We can make sure it behaves as desired:
createApi.exec("hello"); // eventually prints "HELLO"
console.log(createApi.response); // probably null
await createApi.exec("goodbye"); // eventually prints "GOODBYE"
console.log(createApi.response); // "GOODBYE"
This is just showing you that you need to await
the result of createApi.exec()
if you want to guarantee that createApi.response
is populated. Also this:
createApi.exec() // error! Expected 1 arguments, but got 0
createApi.exec(123); // error! Argument of type 'number' is not
// assignable to parameter of type 'string'.
demonstrates that the compiler will be unhappy if you don't pass in the right arguments to createApi.exec()
, which was the original point of your question.