In TypeScript (3.6.3) Generator<> is almost identical to IterableIterator<>. When Generator<> extends Iterator<>, it defaults the third generic argument (TNext) to unknown. Iterator<> by itself defaults TNext to undefined. So Generator and Iterator (and IterableIterator) don't line up as well as they might should.
let gen2:IterableIterator<string>;
function* gen1():Generator<string> {
yield* gen2;
}
The yield* line is an error: "Cannot delegate iteration to value because the 'next' method of its iterator expects type 'undefined', but the containing generator will always send 'unknown'. ts(2766)".
Am I missing something? Is there a good reason for this?
This is actually a very complicated issue. I do not pretend to understand it fully. But perhaps I can offer some insight.
(in this commit). Typescript decided, for better or for worse, it wants stricter type checks for generators. There are actually some valid reasons for this. Take the following example
function* foo() {
let m = 0;
while (m < 10) {
yield m++;
}
return "done";
}
let gen = foo(),
curr;
while(!(curr = gen.next()).done) {}
// At his point we should know that
// curr.value is a string because curr.done is true
Here we can see the problem — we can't know if a value was returned or yielded yet by all rules of logic we should. So they introduced TReturn. TNext was introduced to:
[…] correctly check, and provide a type for, the result of a
yield
expression based on the next type of the generator's return type annotation (i.e. theTNext
type in theGenerator
definition above).
Now, if you have decided to make such changes you are probably going to break some code — the goal is to break as little as possible.
We must note, there are idiomatic differences in the use of the next()
function in generators and non-generator iterators. As the ECMA-262 remarks for iterators.
Arguments may be passed to the
next
function but their interpretation and validity is dependent upon the target Iterator. The for-of statement and other common users of Iterators do not pass any arguments, so Iterator objects that expect to be used in such a manner must be prepared to deal with being called with no arguments.
Iterators are predominantly used in for-of loops which do not pass an argument to next. Indeed, it is vary rare to pass an argument to the next function (MDN even calls it a "A zero-argument function"). Therefore the only sensible option for the default value of TNext
would be undefined
. Making it unknown
would be a great hindrance to type checking (not to mention code compiled with --strictNullChecks
).
That would all be great if passing an argument to the next()
function with generators wasn't a pretty common practice — it actually has a valid use case… and defined behavior in the standard:
Generator.prototype.next(value)
The
next
method performs the following steps:
- Let g be the this value.
- Return ?GeneratorResume(g, value, empty).
and in MDN:
The value to send to the generator.
The value will be assigned as a result of a
yield
expression. For example, invariable = yield expression
, the value passed to the.next()
function will be assigned tovariable
.
Not to mention, in a typical use case, the first .next()
call would be called without an argument and subsequent ones will be called with. Unfortunately, there is no way to specify "optional first time, unknown subsequent times" kind of type, so I guess in light of all this they settled for unknown
for the TNext
in Generators.
Of course there is no perfect situation. But they have to settle for what they believe would be least problematic.
All those problems are discussed in this issue for anyone interested.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With