Home > Net >  Problem with type of function dynamic param in typescript
Problem with type of function dynamic param in typescript

Time:09-09

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.

Playground link to code

  • Related