javascriptnode.jses6-generator

Calling generator function from setTimeout


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)

Solution

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