function* foo() {
yield 123
};
// - - -
function* foo() {
return yield 123
};
I can’t seem to demonstrate the difference between the two.
- Is there a demonstrable difference?
- Should
returnbe used in a generator?
-
1thanks for ask, many answer help me to know more about JS Generators.yussan– yussan2018年01月07日 16:00:01 +00:00Commented Jan 7, 2018 at 16:00
2 Answers 2
First, I'll start by saying that Generators are a somewhat complicated topic, so giving a complete overview here won't be possible. For more information I'd highly recommend Kyle Simpson's You Don't Know JS series. Book 5 (Async & Performance) has an excellent discussion on the ins and outs of generators.
Onto the specific example you gave though!
First, the code that you wrote in the example will show no difference but only if it's run correctly. Here's an example:
function* foo() {
yield 123;
}
function* bar() {
return yield 123;
}
var f = foo();
var b = bar();
f.next(); // {value: 123, done: false}
f.next(); // {value: undefined, done: true}
b.next(); // {value: 123, done: false}
b.next(); // {value: undefined, done: true}
As you can see, I'm not calling the generator like a normal function. The generator itself returns a generator object (a form of iterator). We store that iterator in a variable and use the .next() function to advance the iterator to the next step (a yield or return keyword).
The yield keyword allows us to pass a value into the generator, and this is where your examples will run differently. Here's what that would look like:
function* foo() {
yield 123;
}
function* bar() {
return yield 123;
}
var f = foo();
var b = bar();
// Start the generator and advance to the first `yield`
f.next(); // {value: 123, done: false}
b.next(); // {value: 123, done: false}
/** Now that I'm at a `yield` statement I can pass a value into the `yield`
* keyword. There aren't any more `yield` statements in either function,
* so .next() will look for a return statement or return undefined if one
* doesn't exist. Like so:
*/
f.next(2); // {value: undefined, done: true}
b.next(2); // {value: 2, done: true}
Notice that f.next(2) will return undefined as a value, whereas b.next(2) returns the number 2.
This is because the value we're passing into the second .next() call is sent into the to the bar() generator, assigned as a result of the yield expression, which appears after a return keyword; that means the value we send into the generator is returned back to us and finishes the generator.
foo() has no explicit return statement, so you don't get the value back (foo() already yielded its only value, so the generator is finished, and the result is { done:true, value: undefined }`.
Note that iterators don't typically consume this return value, even though this answer demonstrates how you could manually get the return value using .next()
Hope that this helps!
2 Comments
bar() does not return 2. It returns a Generator object (as you already pointed out above). However, the final b.next(2) call returns an object with its value prop set to 2. That is because, when calling next() after the final yield (i.e. the final pause/interruption), it runs the function to completion, and its final value is the function's return value..next() call passes values into the generator, which are substituted into the code, as if replacing the yield, as code resumes from the point of yield... (not the first .next() call; that value is ignored, as mentioned in comments to the accepted answer, this question, and mentioned here The difference is the result value of the last continuation call:
function* fooA() {
yield 123
};
var a = fooA();
console.log(a.next(1)); // {done:false, value:123}
console.log(a.next(2)); // {done:true, value:undefined}
function* fooB() {
return 40 + (yield 123)
};
var b = fooB();
console.log(b.next(1)); // {done:false, value:123}
console.log(b.next(2)); // {done:true, value:42}
Most generators don't need a return value, their purpose is the generation of a value stream as a side effect when they are running. All iterators are of this kind, if they are ran by a for of loop the result just signifies the end, but the value is discarded.
However, there also are generators where a result value is important, e.g. when they are used as a tool to describe asynchronous processes (in a polyfill for async/await promise syntax, or also many more things like CSP). You also get the returned value when using yield* on an iterable.
In any case, return yield together doesn't sound very useful.
6 Comments
yield excuted two times?.next(1) call is discarded. The final result comes from adding 40 and 2.return yield together doesn't sound very useful", I found this SO question while searching for the Observable technique to pause a generator. I know "Observable is not JS", but this answer's still useful! Also even in vanilla JS generators, a return statement is implied when omitted. So return yield might be a useful pattern yield ...; return, ensuring to yield at least a single value before pausing. In the latter example, yield seems to have been (ab)used to mean await, which is unnecessary before return in case of implicitly-unwrapping promises, but can occasionally be helpful for clarity or type correctness.if (paused) { yield i; return; }