typed-redux-saga icon indicating copy to clipboard operation
typed-redux-saga copied to clipboard

yield* call sometimes infers type as Generator

Open rssfrncsrenishaw opened this issue 4 years ago • 13 comments

const { d1, d2  } = yield* all({
    d1: call(fetchD1, options),
    d2: call(fetchD2, options),
  });

I've had sitatuions where the above example fetchD1 and fetchD2 are both other sagas that I am calling with call but fetchD1 infers the correct type ({ from: string; to: string; featureName: string | undefined; name: string; data: MeasurementCharacteristicPresentation[]; }) and fetchD2 infers,

Generator<SelectEffect | AllEffect<SagaGenerator<{
    from: string;
    to: string;
    featureName: string | undefined;
    name: string;
    data: MeasurementCharacteristicPresentation[];
}, CallEffect<...>>>

The difference between the two sagas return,

fetchD1

return yield* call(
    (from, to, take, machineId) =>
      client.fetchD1Data.unique({
        query: {
          from,
          to,
          take,
          machineId
        }
      }).promise,
    from,
    to,
    constants.maxTake,
    machineId
  );

fetchD2

return yield* all(
    items.map(({  name }) =>
      call(fetchD2Data, { from, to,  name })
    )
  );

rssfrncsrenishaw avatar Mar 18 '20 09:03 rssfrncsrenishaw

Doing yield* yield* call() works... so it does seem to think i'm returning a generator though i'm not sure how I am as I am doing return yield* all

rssfrncsrenishaw avatar Mar 18 '20 10:03 rssfrncsrenishaw

works in the sense it stops typescript complaining but breaks runtime behaviour

rssfrncsrenishaw avatar Mar 18 '20 15:03 rssfrncsrenishaw

I may have caused this in https://github.com/agiledigital/typed-redux-saga/issues/13

danielnixon avatar May 04 '20 04:05 danielnixon

Could you formulate this as a dtslist $ExpectType failure in index.test.ts?

danielnixon avatar May 22 '20 23:05 danielnixon

Hi @danielnixon you can see an example in #92 . Thanks!

PS more than happy to tidy up the PR if you find it useful

kristian-puccio avatar May 25 '20 07:05 kristian-puccio

hi is there any update on this?

rssfrncsrenishaw avatar Jul 28 '20 07:07 rssfrncsrenishaw

PRs welcome

danielnixon avatar Jul 28 '20 10:07 danielnixon

I was struggling with this as well, and in the end it was my fault, because I was calling a saga which yielded a mixture of generators and non-generators in places where there is conditional program flow - because I forgot to check all the branches and to turn some of the yield to yield*.

TLDR: If you have this issue, first go and check all the callees recursively - you might have forgot to change yield to yield* somewhere and it waits for you to fix it.

Real example:

export function* parseSaga({ threadParse, input }: ParseParams) {
  yield put(parseAsync.started())
  try {
    const result = yield* call(threadParse, input)
    yield put(parseAsync.done({ result }))
    return result
  } catch (error) {
    yield put(parseAsync.failed({ error: sanitizeError(error) }))
  }
  return undefined
}

const parseResult = yield* call(parseSaga, { threadParse, input })
// type of parseResult is inferred as `Generator<bla bla bla>`


Notice how in parseSaga() I use yield* call(threadParse, input), like typed-redux-saga readme suggests, but I forgot to change all 3 yield put(...). The put() from typed-redux-saga returns a generator and you must use yield* with it. And if you now, then the type of the entire saga becomes a generator. So, if I change those to yield* put(...) (with an asterisk), then the type is inferred correctly.

It is a super silly mistake to make, but took me some time to figure. Hope it helps someone to resolve their type errors.

ivan-aksamentov avatar Aug 14 '20 04:08 ivan-aksamentov

Thanks @ivan-aksamentov that's a good insight. I wonder if there's something we can do with eslint to catch that 🤔

danielnixon avatar Aug 14 '20 08:08 danielnixon

That might also explain #27

danielnixon avatar Aug 14 '20 08:08 danielnixon

I ran into this when using eventChannel which isn't typed

If anyone needs a hack to get around this issue you can do something like this

const foo = *function() {
      // @ts-ignore
      const something = yield* call(bar) as ReturnType<typeof barTyped>;

     // something now has correct type
}

// Do not call.  Used for typing
const barTyped = function *() {
  return yield* yield* call(bar);
};

const bar = function *() {
   return yield call(something)
}

Avoiding yield is still the best choice if you have that option though

nzankich avatar Mar 15 '21 01:03 nzankich

@danielnixon I ran into this with race - Could this be because the RaceEffect<T[keyof T]> of the second type param to SagaGenerator being a RaceEffect<SagaGenerator<...>> and not a RaceEffect<CallEffect> etc? So the generator thinks it yields generators and not effects? I think it should be something like: RaceEffect<T[keyof T] extends SagaGenerator<any, infer E> ? E : T[keyof T]> right?

Does that seem right?

https://github.com/agiledigital/typed-redux-saga/blob/bec739d2c418559e1a4247ea1e4576d753f17f3b/types/index.d.ts#L567

  const raceEffect = race({
    timeout: delay(timeoutMs)
  })

has type:

const raceEffect: SagaGenerator<{
    timeout: true | undefined;
}, RaceEffect<SagaGenerator<true, CallEffect<true>>>>

but I think it should have the type:

const raceEffect: SagaGenerator<{
    timeout: true | undefined;
}, RaceEffect<CallEffect<true>>>

rickyrombo avatar Jul 20 '22 07:07 rickyrombo

I think I may be running into this issue too. I opened a draft PR with a minimal example as a failing test: #669.

Test code repeated here for convenience:

  // $ExpectType boolean
  yield* Effects.call(function* () {
    yield* Effects.race({ timeout: Effects.delay(1) });
    return true;
  });

Test output for convenience:

[email protected] expected type to be:
  boolean
got:
  Generator<RaceEffect<SagaGenerator<true, CallEffect<true>>>, boolean, unknown>

pepijn-motosumo avatar Sep 27 '22 15:09 pepijn-motosumo