Calling generator function from setTimeout

2.8k views Asked by At

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)
1

There are 1 answers

8
jfriend00 On BEST ANSWER

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);