TypeScript: Type 'T' does not satisfy the constraint '(...args: any) => any'

Viewed 13772

I have the following redux-thunk action creator:

function updateInitiator(form_request_id, { to_recipient }) {
  return (dispatch) => {
    const url = `/some/url`;
    const data = { to_recipient };

    return fetch(url, { method: 'PUT', body: JSON.stringify(data) }).then(() => {
      dispatch(fetchResponses());
    });
  };
}

Then I declare the type of the function:

type UpdateInitiator = typeof updateInitiator;

I'm trying to derive the type of a bound thunk action. In short, when the action creator is "bound" in react-redux, it automatically calls the function returned with dispatch and then returns the return result of that internal function. I'm trying to declare a type for this behavior. It works if I do it without generics:

type BoundUpdateInitiator = (...args: Parameters<UpdateInitiator>) => ReturnType<ReturnType<UpdateInitiator>>;

But when I try to declare a generic type for any bound function, I'm having some trouble:

type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
type BoundUpdateInitiator = BoundThunk<UpdateInitiator>;

This gives me the error:

error TS2344: Type 'T' does not satisfy the constraint '(...args: any) => any'.

236 type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
                                              ~

error TS2344: Type 'ReturnType<T>' does not satisfy the constraint '(...args: any) => any'.
  Type 'unknown' is not assignable to type '(...args: any) => any'.
    Type '{}' provides no match for the signature '(...args: any): any'.

236 type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
                                                                ~~~~~~~~~~~~~

error TS2344: Type 'T' does not satisfy the constraint '(...args: any) => any'.

236 type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
                                                                           ~

I can vaguely understand that T might be things other than a function, and similarly ReturnType<T> might also not be a function, and this generic type maybe doesn't account for those cases. I'm having trouble understanding how I can account for them, however. Ideally by not allowing them. Any suggestions?

1 Answers

In order to accomplish this we would use a generic type constraint.

This is in fact how the type parameters of the language provided utility types Parameters<T> and ReturnType<T> are specified thus causing the error you receive.

In order to instantiate a generic type, say ReturnType<T>, with our type parameter, our type parameter must constrained at least as restrictively as the type parameter T in ReturnType<T>.

By looking at the definition of ReturnType<T> we can determine the minimum constraint (formally known as the upper bound), we need to apply and the associated syntax

type ReturnType<T extends (...args: any[]) => any> =
    // details

Don't worry about the implementation (after the =) as that is a broader topic. In this situation, we will focus on the constraints of T specificied using the extends keyword at the declaration of T.

Therefore, in order to pass the T declared by BoundThunk<T> to ReturnType<T> we must constrain it to meet the requirements above (note that Parameters<T> has identical constraints).

type BoundThunk<T extends (...args: any[]) => any> =
    (...args: Parameters<T>) => ReturnType<ReturnType<T>>;

However, our requirements for T are actually more restrictive because we apply ReturnType<T> twice, ReturnType<ReturnType<T>>, implying that T is a higher order function, in this case a function that returns a function.

We will therefore refine our constraint accordingly

type BoundThunk<T extends (...args: any[]) => (...args: any[]) => any> =
    (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
Related