2

The following js code fails in developer console of firefox, chrome and nodejs as well. Unable to figure out why?

function* x() {}
let y = x()
setTimeout(y.next, 100)

Error in firefox

TypeError: CallGeneratorMethodIfWrapped method called on incompatible Window

Error in chrome

Uncaught TypeError: Method [Generator].prototype.next called on incompatible receiver # at next ()

Error in node.js

timers.js:475
    timer._onTimeout();
          ^

TypeError: Method [Generator].prototype.next called on incompatible receiver #<Timeout>
    at Timeout.next [as _onTimeout] (<anonymous>)
    at ontimeout (timers.js:475:11)
    at tryOnTimeout (timers.js:310:5)
    at Timer.listOnTimeout (timers.js:270:5)
chinoy
  • 172
  • 1
  • 13

1 Answers1

3

The object y is lost when you pass y.next as the function to be called. You can do this:

setTimeout(y.next.bind(y), 100)

When you pass y.next, it reaches onto the y object and gets a reference to the next function and it passes just a reference to the next function. It's a generic reference to the next function that has no association at all with the y object. Then, later when the setTimeout() fires, it just calls the next function all by itself and the object y is not used in the function call. That means that when next executes, the this value will be undefined and will not be the appropriate y object.

You can imagine it doing this:

let x = y.next;
setTimeout(x, 100);

Nothing to do with y was passed to setTimeout(). It's going to call that next() method as a normal function. You could see the same problem if you did this:

let x = y.next;
x();

By its design, setTimeout() just calls functions as in fn(). It doesn't call methods as in y.next(). To call a method, the object reference has to be used in the actual function call as you see in y.next(). setTimeout() does not do that. It just calls functions.

So, .bind() creates a small little stub function that will then call it properly for you. So, using it as I showed above is analogous to this:

let x = function() {
    y.next();
}
setTimeout(x, 100);

Or, the inline version:

setTimeout(function() {
    y.next();
}, 100);
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    Or pass a lambda: `setTimeout(() => y.next(), 100)`. @VLAZ: definitely, I'm slightly biased to use lambdas more than to anonymous functions, but thats a valid option – Caramiriel Mar 20 '19 at 17:53
  • @Caramiriel even a normal function would work `function() { y.next() }` - in either case the invocation will have the context set correctly. – VLAZ Mar 20 '19 at 17:55
  • @jfriend00 Thanks for the answer. Could you please explain why the context of y is necessary? y.next is already a function, so why invocation of y.next fails. – chinoy Mar 20 '19 at 18:02
  • 2
    "*y.next is already a function*" correction it's a **method** and that is precisely why it matters. A quick explanation of terminology would help - a *function* is an unbound procedure that (generally) takes some input and produces an output. A *method* is nearly the same when looked from afar - it still takes input and produces an output but it's bound to an *object*. So, in short if you are calling `someObj.something()` it's a method, if you are calling `something()` it's a function. A method may depend on its context - the object. Thus disassociating the context can break a method. – VLAZ Mar 20 '19 at 18:08
  • @chinoy probably the most important difference is that *methods* would be using `this` to access the object they belong to. Which is why `this` is called the context - it's the part of the procedure that's *outside* the procedure's current scope but the code uses stuff. E.g. calling `this.someVariable++`. No context and the code breaks or behaves unexpectedly. A method might not use the context, in which case it's safe to divorce it but it's not easy to say if it does or not. – VLAZ Mar 20 '19 at 18:12
  • the more correct use would be [`Generator.prototype.next`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next) and this requires a binding of `this`. – Nina Scholz Mar 20 '19 at 18:14
  • thanks @VLAZ. Makes sense as with yield*, the generator function delegates to another generator function. – chinoy Mar 20 '19 at 18:14
  • @chinoy - I added more explanation to my answer. The boss came a callin' so I was away for a bit. – jfriend00 Mar 20 '19 at 18:24