Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS Generators: How is `return yield` different from `yield`?

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?
like image 550
Kirk Strobeck Avatar asked Feb 17 '17 23:02

Kirk Strobeck


People also ask

What is the difference between yield and return JavaScript?

Return simply returns the value after the function call, and it will not allow you to do anything else after the return statement. Yield works different. Yield returns a value only once, and the next time you call the same function it will move on to the next yield statement.

Does yield return a generator?

When you use a yield keyword inside a generator function, it returns a generator object instead of values. In fact, it stores all the returned values inside this generator object in a local state.

What is yield in generator function?

The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword. yield can only be called directly from the generator function that contains it.

Does generator function need yield?

The yield * expression is used with generator functions or iterables. If the iterable is empty or the generator function has no yield keyword, the iterator returned from the main generator function call will be completed with the first call to next() and will never pause in between the yield * calls.


2 Answers

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 foo() will return undefined as a value, whereas bar() returns the number 2. This is because the value we're passing into the .next() call is sent to the return keyword and set as the return value. foo() has no explicit return statement, so you get the default undefined behavior.

Hope that this helps!

like image 61
Joshua Kleveter Avatar answered Sep 20 '22 21:09

Joshua Kleveter


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.

like image 30
Bergi Avatar answered Sep 21 '22 21:09

Bergi