Skip to content
Advertisement

How to type a generator function with typscript

I would like to send a value back to the generator function using next which seems to complicate typing this function

function* gen(): IterableIterator<string> {
    const a = yield 'test';       // Line A
    console.log('received:' + a);
    yield a + '';
}

const x = gen();

console.log('main:' + x.next(99));  // Line B
console.log('main' + x.next());

DEMO

In my VSCODE I get the following error for Line A

Type 'undefined' is not assignable to type 'number'.

And In Stackblitz/demo I get an error for Line B

Argument of type '[99]' is not assignable to parameter of type '[] | [undefined]'.
  Type '[99]' is not assignable to type '[undefined]'.
    Type '99' is not assignable to type 'undefined'.(2345)

So my question is, how can I type the value I provide with next?

Advertisement

Answer

TypeScript 3.6 introduced support for stricter generator typing, including the Generator<Y, R, N> type where the Y type parameter corresponds to the type yielded from the generator function body (the same as the T in Iterator<T>), the R type parameter corresponds to the type returned from the generator function body, and the N type parameter corresponds to the type passed into the next() method of the iterator. Since you are passing string to yield and passing number to next and not returning anything, it looks like you want your generator return type to be something like Generator<string, void, number>:

function* gen(): Generator<string, void, number> {
    const a: number = yield 'test';
    console.log('received: ' + a);
    yield a + '';
}

const x = gen();
const y = x.next(99);
if (y.done) throw new Error();
console.log("main: " + y.value) // main: test
const z = x.next(); // received: undefined
if (z.done) throw new Error();
console.log("main: " + z.value) // main: undefined

It is a little weird that a is typed as number but could be undefined, even with the --strictNullChecks compiler option enabled. But that’s what happens if you call x.next() without an input. This is apparently working as intended as per this comment on ms/TS#30790, the implementing pull request. So if you ever plan to do something that would explode if undefined comes out of that yield, like this:

function* gen(): Generator<string, void, number> {
    const a: number = yield 'test';
    console.log('received:' + a.toFixed(2)); // ERROR if a is undefined
    yield a.toFixed(2);
}

then you should probably manually augment the N type parameter with undefined to be safe:

function* gen(): Generator<string, void, number | undefined> {
// -------------------------------------------> ^^^^^^^^^^^
    const a = yield 'test';
    console.log('received: ' + (a?.toFixed(2)));
    yield a?.toFixed(2) || "undefined";
}

const x = gen();
const y = x.next(99);
if (y.done) throw new Error();
console.log("main: " + y.value) // main: test
const z = x.next(); // received: undefined
if (z.done) throw new Error();
console.log("main: " + z.value) // main: undefined

Playground link to code

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement