0

I have a resolver function which might have an optional second parameter called otherParams. I would like that Typescript checks if the function returned from createResourceThunk is also called with the parameter otherParams if it is defined in the resolver function. How can I do that? Minimal reproducable example here

export function createResourceThunk<T, Params, A, OtherParams extends A | never>(
  actions: ResourceActions<T>,
  resolver: (params: Params, otherParams: OtherParams) => Promise<T>
) {
  return function(params: Params, callbacks: Callbacks<T>, otherParams: OtherParams) {
    return async (dispatch: Dispatch) => {
      dispatch(actions.startAction());
      try {
        const data = await (otherParams ? resolver(params, otherParams) : resolver(params));
        ...
      } catch (error) {
        ...
      }
    };
  };
}

Currently it is expecting always a third Parameter in the call of the returned function

velop
  • 3,102
  • 1
  • 27
  • 30
  • 3
    Please consider modifying the code here so as to constitute a [mcve] suitable for dropping into a standalone IDE like [The TypeScript Playgound](https://tsplay.dev/yNag9W) where others can demonstrate your issue (and only your issue) for themselves. It is much easier to help if I can start from your problem instead of having to re-create your problem. – jcalz Feb 03 '21 at 16:27
  • What are you doing with `A | never`? That is immediately reduced to `A` no matter what. Also, in your [mcve] I'd suggest showing a few use cases of calling `createResourceThunk()` and calling what it returns, with `resolver` parameters that do / do not take a second parameter, so that folks can demonstrate what's supposed to be happening. Otherwise you will get the answer "just make `otherParams` optional everywhere with a `?`". – jcalz Feb 03 '21 at 16:35
  • 1
    Does [this](https://tsplay.dev/BmxAbW) work for your use cases? If not, please elaborate, preferably with a [mcve] that shows what you need. (Hmm, have I mentioned that you should provide a [mcve]? Is there an echo in here? ) – jcalz Feb 03 '21 at 16:38
  • @jcalz Haha, your are absolutely right with the echo and also that I should have created a minimal r.e. I'll add it to the question. Thank you pointing to the typescript playground for this – velop Feb 05 '21 at 10:44
  • Btw. the type AddBetween is taken from https://stackoverflow.com/questions/53985074/typescript-how-to-add-an-item-to-a-tuple – velop Feb 05 '21 at 12:07
  • Please edit the code in the text of the question to reflect what's going on in your [mcve]; I'm writing up an answer now. – jcalz Feb 05 '21 at 15:04

1 Answers1

1

Given the code in your linked example, I'd be inclined to use the following typing:

export function createResourceThunk<T, P, O extends [] | [otherParams: any]>(
  actions: ResourceActions<T>,
  resolver: (params: P, ...args: O) => Promise<T>
) {

  return function (params: P, callbacks: Callbacks<T>, ...otherParamsArr: O) {
    const [otherParams] = otherParamsArr; // if you care
    return async (dispatch: Dispatch) => {
      dispatch(actions.startAction());
      try {
        const data = await resolver(params, ...otherParamsArr);
        dispatch(actions.successAction({ data }));
        callbacks && callbacks.onSuccess && callbacks.onSuccess({ data });
      } catch (error) {
        dispatch(actions.errorAction({ error }));
        callbacks && callbacks.onError && callbacks.onError({ error });
      }
    };
  }
}

The important piece here is that the "optional" second parameter is represented as a rest argument, whose type is a union of rest tuple types. The type parameter O is the list of parameters after the params argument. It is constrained to either have zero elements ([]) or one element ([otherParams: any]). So anywhere before where you'd want to say otherParams you would use a rest parameter like ...otherParamsArr.

That works inside the implementation with no errors or type assertions necessary. You can verify that this also works from the caller's side:

// with OptionalParam
const func1 = createResourceThunk('any', (a: { a: number }, b: string) => Promise.resolve('any'))
func1({ a: 1 }, { successAction: () => { } }, 'somestring') // okay
func1({ a: 1 }, { successAction: () => { } }); // error

// without OptionalParam
const func2 = createResourceThunk('any', (a: { a: number }) => Promise.resolve('any'))
func2({ a: 1 }, { successAction: () => { } }) // okay
func2({ a: 1 }, { successAction: () => { } }, 'somestring') // error

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Thx for your help. It does a wonderful job – velop Feb 16 '21 at 17:22
  • Hi @jcalz, hope your doing well. I have another interesting question regarding typescript and maybe you've got an answer: https://stackoverflow.com/questions/69264949 – velop Sep 22 '21 at 12:25