Best way to describe this is with an example:
declare var run: () => void
type Result = {
id: string,
value: number
}
function setBreakpoint() : Promise<Result> {
return new Promise((resolve, reject) => {
resolve({ id: "hi", value: 5 });
});
}
function* foo() : Generator<Promise<Result>, Object, Result> {
const { id } = yield setBreakpoint();
return { id: id };
}
console.log(run(foo()));
The above gives this error:
main.js:52
52: const { id } = yield setBreakpoint();
^^ property `id`. Property cannot be accessed on possibly undefined value
51: function* foo() : Generator<Promise<Result>, Object, Result> {
^ undefined
If I change the setBreakpoint line to this:
const { id } = (yield setBreakpoint(): Result);
It's clear that somehow the result of the yield is getting an undefined type:
main.js:51
51: function* foo() : Generator<Promise<Result>, Object, Result> {
^ undefined. This type is incompatible with
52: const { id } = (yield setBreakpoint(): Result);
^^^^^^ object type
I think it's also not pointing to the right place where it's getting the undefined type.
BTW, this works:
function* foo() {
const { id } = (yield setBreakpoint(): any);
return { id: id };
}
But for some reason this still doesn't:
function* foo() : Generator<any, any, any> {
const { id } = yield setBreakpoint();
return { id: id };
}
@jlongster I'm having this issue too, but your with your solution I get an Unexpected token error on
(yield setBreakpoint(): any). What node and flow versions are you on? I have 0.28.0 for flow, and 6.2.1 for node.
@eliaslfox That's strange, I'm on flow 0.28.0 and node 6.2.2. I can't think of anything else that would make this work for me but not for you.
@jlongster Weird, will have a look soon.
Actually, this appears to be by design. See https://github.com/facebook/flow/pull/534#issuecomment-114284266
Briefly, yield returns something of type Result | void in your example, where the void is there to handle a corner case. @samwgoldman Want to jump in and clarify?
Separately, I think we might want to reconsider this decision. cc @gabelevi who was also involved in the design decision...
Yes, this is intentional and reflects the fact that the argument to next is optional.
function *g() {
console.log("foo");
var x = yield;
console.log("bar", x);
}
var gen = g(); // nothing happens
gen.next(); // "foo"
gen.next("value"); // "bar value"
No part of the generator function is evaluated until the first call to next, which takes 0 arguments. The argument to subsequent calls to next are returned from yield expressions.
This is why Flow models the argument to next as optional, and consequently why it models yield expressions as similarly optional.
If generators worked slightly differently and instead evaluated up to the first yield expression eagerly, then next would always take an argument and we wouldn't have this issue. Alas!
I am still open to relaxing this (read: introducing unsoundness) to provide greater convenience.
when using with some libraries like co https://github.com/tj/co or redux-saga
https://github.com/yelouafi/redux-saga that use generator to control the flow of async calls
it would be nice no to have to force cast the return value like in the following example
declare var getFoo: () => Promise<string>
co(function *(): Generator<*, *, *> {
const foo: string = yield getFoo()
// instead of const foo: string = ((yield call(getFoo)): any)
})
I agree with @pgherveou - in fact, I didn't have a clue that was what I needed to do until I found this issue. It would definitely make things a lot cleaner with fewer ( )s.
@jlongster I'm having this issue too, but your with your solution I get an Unexpected token error on
(yield setBreakpoint(): any). What node and flow versions are you on? I have 0.28.0 for flow, and 6.2.1 for node.
I'm getting this as well without the extra parens around yield .... In other words:
const { id } = (yield setBreakpoint(): any);
Does not parse but
const { id } = ((yield setBreakpoint()): any);
Does.
It's a babel error, which probably means that the types aren't getting stripped or babel doesn't know how to handle it.
As for the actual issue here, I'm not sure I understand why it's not possible to type the return of yield without undefined. Even if next can always take no arguments on its first call, isn't it possible to say that yield must still always return something of the Yield type?
Edit: ah, it has to do with return types instead. value can be undefined if done is true. I wonder though because by definition, what comes out of yield should have done be false. Given that https://github.com/facebook/flow/issues/577 was done, shouldn't this be fixable?
Edit again: This stuff is confusing, heh. What comes out of the yield is actually the Next type, so the whole done thing doesn't matter. I get that now. I don't quite see why flow can't assume that the Next type instead of Next | undefined for the return from yield within a generator. I get that next() could be called by the consumer of the generator without an argument, but that would be the fault of the caller, of which we often do not have control.
A potential alternative to the parenthesis:
function cast<T>(t: any): T {
return t
}
function *foo() {
const z: number = cast(yield "hi")
}
Hey! This is bug!
type Result = {
id: string,
value: number
}
function setBreakpoint() : Promise<Result> {
return new Promise((resolve, reject) => {
resolve({ id: "hi", value: 5 });
});
}
function* foo() : Generator<Promise<Result>, Object, Result> {
const { id } = yield setBreakpoint();
return { id: id };
}
foo generator can only receive Result object, why this error here:
const { id } = yield setBreakpoint();
^^ property `id`. Property cannot be accessed on possibly undefined value
51: function* foo() : Generator<Promise<Result>, Object, Result> {
^ undefined
This behaviour obvious, if I use foo generator outside and Yield values from this:
const id : string = (foo.next()).value
Ok, here foo generator can be ended and return undefined in value.
But this case can be wrong too, because I can have foo as infinite generator, that always return string and done already is false and I should mark this via flowtype like something.
Most helpful comment
when using with some libraries like co https://github.com/tj/co or redux-saga
https://github.com/yelouafi/redux-saga that use generator to control the flow of async calls
it would be nice no to have to force cast the return value like in the following example