5

In the following code:

function so() {
  console.log('inside the timer')
}

function* sogen() {
  const callback = yield;

  setTimeout(callback, 2000);

  return 1;
}

function() {
  var gen = sogen();
  gen.next(so), gen.next(so);
}()

Why am I never reaching function so?

lucascaro
  • 16,550
  • 4
  • 37
  • 47
AnArrayOfFunctions
  • 3,452
  • 2
  • 29
  • 66
  • let response = await soasync(); – Osama Oct 16 '18 at 22:18
  • 2
    `yield` itself is not being used as a callback. `yield` produces a value (whatever was passed to `.next()`) and the value is assigned to `callback`. – Ry- Oct 16 '18 at 23:18
  • 1
    Please don't change question, @Aioros answer is weird now. It is better to create another question – Buggy Oct 17 '18 at 08:50
  • add parentheses around IIFE function `(function(){ var gen = sogen(); gen.next(so), gen.next(so); })()` – Buggy Oct 17 '18 at 08:51

4 Answers4

4

tl;dr you need to wrap your IIFE with parentheses or to not use an IIFE at all.

Your use of generators is fine, and once you add the parens, everything works as normal.

Note that you don't really need a IIFE to run your code, but my answer below explains why what you have is not working.

Function declarations vs Function expressions

The main problem you have is in this code:

function() {
  var gen = sogen();
  gen.next(so);
  gen.next(so);
}()

This will produce an error similar to:

Uncaught SyntaxError: Unexpected token (

The problem here is that you are trying to use a function declaration as a function expression.

From MDN (emphasis mine):

A function expression is very similar to and has almost the same syntax as a function statement (see function statement for details). The main difference between a function expression and a function statement is the function name, which can be omitted in function expressions to create anonymous functions. A function expression can be used as a IIFE (Immediately Invoked Function Expression) which runs as soon as it is defined. See also the chapter about functions for more information.

This means that in order to execute a function immediately, you need to use a function expression rather than a statement.

One common way of writing a function expression is to wrap the function in parentheses:

function a() { return 'a'; } // Function declaration
(function b() { return 'b'; }) // Function expression

To convert that into an IIFE, you can add the () invocation parens at the end:

(function c() { return 'c'; })() // IIFE

which calls the function immediately. Note that I prefer to put the invocation parentheses inside the wrapping parens but this is just a stylistic choice and works in the same way:

(function c() { return 'c'; }()) // IIFE

Here's the code from the answer, plus the parens wrapping the IIFE:

function so() {
  console.log('inside the timer');
}

function* sogen() {
  const callback = yield;

  setTimeout(callback, 2000);

  return 1;
}

(function() {
  const gen = sogen();
  gen.next(so);
  gen.next(so);
}())

Alternatively, simply remove your IIFE:

const gen = sogen();
gen.next(so);
gen.next(so);

or if you need a function declaration, call the function on the next line:

function run() {
  const gen = sogen();
  gen.next(so);
  gen.next(so);
}
run();
lucascaro
  • 16,550
  • 4
  • 37
  • 47
  • 1
    Why do we need `(function() { code here }())` wrapping to execute the code? – shukshin.ivan Nov 11 '18 at 20:20
  • You raise a good point. You really don't need that at all, but I kept it in there because that was causing the original problem. I'll add a note to clarify that it's not really needed. Thanks! – lucascaro Nov 11 '18 at 20:23
3

The snipped you provided should now be working (except for the syntax error in the IIFE). I have rewritten it for clarity.

function so() {
  console.log('inside the timer')
}

function* sogen()
{
  const callback = yield; // line 1
  setTimeout(callback, 2000); // line 2
  return 1; // line 3
}

Now let's see how using the iterator returned from sogen we can call so.

var iter = sogen();

We have created an iterator. Calling the next method of the iterator we can advance the execution of the sogen generator.

iter.next();

After this call the state of the iterator is now frozen on line 1 of sogen. The yield was encountered and {value: undefined, done: false} was returned from the .next() call. At this point we are ready to pass our callback.

iter.next(so);

We have passed the callback into the next method and the execution resumes at line 1. The callback variable has now the value of so function. Continuing onto line 2 - setTimeout is called. In two seconds our so function will be called. But before it is the code continues to line 3. The .next(so) call returns {value: 1, done: true}. Now we wait.

After two seconds you should see that inside the timer has been logged to the console.

Niebieski
  • 125
  • 6
2

In your snippet, sogen is not a regular function, it's a generator, as indicated by the *.

You can call the generator to obtain what is basically an iterator, that can be controlled with the method .next() and can pause its execution every time it encounters the yield keyword.

var it = sogen();
it.next();

The assignment you have there (const callback = yield) will be resolved by the subsequent .next() call, like:

it.next(function myCallback() { /* ... */ });

The generator will then keep running until the next yield or the end of the function.

Aioros
  • 4,373
  • 1
  • 18
  • 21
  • But when will the `myCallback` function be invoked? – AnArrayOfFunctions Oct 17 '18 at 00:06
  • In your case, it will be called six seconds after the `.next()` invocation resolves the `yield`. Basically the generator is there waiting for a value, someone passes it with `.next()`, the generator resumes execution and sets the six-second timeout. – Aioros Oct 17 '18 at 00:13
0

Here you are using generator function as an observer.

when you create a generator object it is not actually invoked.

when you call gen.next(so) for the first time, it acts as an invoking call to the generator and the value passed to it is ignored.(the first invocation advances execution to the first yield.)

on the second call to gen.next(so) yield receives 'function so(){}' and the rest of the code is executed.

Please have a look at this link to have more clarity : generator function as observer

Your working snippet

Shubham
  • 1,740
  • 1
  • 15
  • 17