41
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 return be used in a generator?
asked Feb 17, 2017 at 23:32
1
  • 1
    thanks for ask, many answer help me to know more about JS Generators. Commented Jan 7, 2018 at 16:00

2 Answers 2

49

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!

Nate Anderson
21.7k22 gold badges113 silver badges155 bronze badges
answered Feb 18, 2017 at 0:00
Sign up to request clarification or add additional context in comments.

2 Comments

There is some small logical flaws in the writing: 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.
Nice example thanks :) I incorporated the feedback from @Domi and added a few more links to explain how the second .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
20

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.

answered Feb 17, 2017 at 23:40

6 Comments

Hmmph... Just can't understand why it finally yields 42, not 41, so is that the yield excuted two times?
@a_a The argument to the first .next(1) call is discarded. The final result comes from adding 40 and 2.
Great answer thank you. You say "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
@TheRedPea In the former example, it seems it's used to the same effect as 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.
@TheRedPea Yes, had I expected you to take me literally I would have written if (paused) { yield i; return; }
|

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.