Search code examples
typescriptiterator

Is there a way I can get the Return Type of the generator function?


I want to get the return type of the generator function.
For example

function* test(){
  yield 'a';
  yield 123;

  return true;
}

// what I'm trying to get
type A = GetGeneratorReturn<test>; // -> A has type boolean

What I'm currently at.

type B = ReturnType<ReturnType<typeof test>['next']>['value'];
// B has type `true | "a" | 123`

(I thought I could get return type if I get the first item of Union type B)


Solution

  • There is an open issue which talks about this problem among others, but it's quite old and it doesn't look like anyone's tending to it since some of the other issues have been addressed. I see recent mentions of it here and there. The most relevant issue out there seems to be this one. If this matters to you, you might want to head over to GitHub and give these issues some 👍s.

    For now it seems that generator typings in TypeScript only give you access to an undifferentiated union of the yield types and the return type. And unfortunately, you can't rely the ordering of a union type. It's an implementation detail of the compiler; since X | Y is equivalent to Y | X, the compiler feels free to change one to the other. And sometimes it does:

    function* test() {
      yield 'a';
      yield 123;
      return true;
    } // function test(): IterableIterator<true | "a" | 123>
    
    function* oops() {
      yield true;
      yield 123;
      return 'a'
    } // function oops(): IterableIterator<true | "a" | 123>
    

    The return type of oops() is exactly the same as the return type of test(). That means your idea of extracting the first/last element of a union is a non-starter.

    It's possible there's a workaround that will help, especially if you can mess with the generator definition and don't mind some phantom types floating around:

    type Returnable<T> = { __returnedType?: T } & T
    const ret = <T>(t: T) => t as Returnable<T>;
    type GeneratorReturn<T extends (...args: any) => IterableIterator<any>> =
      ReturnType<T> extends IterableIterator<infer I> ?
      NonNullable<Extract<I, Returnable<unknown>>['__returnedType']> : never
    
    function* test() {
      yield 'a';
      yield 123;
      return ret(true); // only use ret() here
    } 
    
    function* oops() {
      yield true;
      yield 123;
      return ret('a'); // only use ret() here
    } 
    
    type TestRet = GeneratorReturn<typeof test>; // true
    type OopsRet = GeneratorReturn<typeof oops>; // "a"
    

    Which works as far as it goes, but might not be something you want to pursue. Oh well, sorry that I can't find a better answer here. Good luck!