Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Andy Matthews and Tim Cunningham and Matt Gifford and Dave Ferguson and Dan Wilson
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Andy Matthews@commadelimited ) , Tim Cunningham@TimCunningham71 ) , Matt Gifford@coldfumonkeh ) , Dave Ferguson@dfgrumpy ) , and Dan Wilson@DanWilson )

Changing The THIS Binding And Execution Context Of A Generator In Node.js

By Ben Nadel on

Now that I'm on one of the Node.js teams at InVision App, I get to start using some of the newer, more advanced features of JavaScript, like Generators and generator functions. In the past, I've looked at how Generators can be used to make asynchronous programming more visually pleasing. But, since I can also use the newer ES6 Class syntax, it got me thinking about the execution context of Generators. Specifically, how we can change the ".this" binding of a Generator object.


 
 
 

 
 
 
 
 

My first thought was to use JavaScript's .call() Function method to change the context of the .next() method during the consumption of the Generator:

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // I create a Generator Object that tests the "this" binding.
  • function* genfunk() {
  •  
  • console.log( chalk.red.bold( "[Generator]" ) );
  • console.log( chalk.red( "[this]:" ), this );
  •  
  • }
  •  
  • var generator = genfunk();
  •  
  • // Now that the Generator Object has been created, let's see if we can change the
  • // context binding of the iterable method at execution time using .call().
  • // --
  • // CAUTION: THIS DOES NOT WORK (KEEP READING THE ARTICLE, BRO)
  • generator.next.call({
  • description: "I was bound to the generator using .next.call()."
  • })
  •  
  • // Let's test to see what ".this" binding is in place.
  • gen.next();

As it turns out, however, this approach doesn't work. It looks like Generators are a bit more "magical" than normal Objects (I mean, obviously, they can arbitrarily yield execution). So, when we try to run the above Node.js code, we get the following terminal error:

TypeError: Method [Generator].prototype.next called on incompatible receiver #<Object>

Once the Generator object is created, we can't mess with the execution context. But, as it turns out, we can use the .call() and .apply() methods to change the execution context at Generator creation time. Meaning, when we call the generator function, that returns the Generator object, we can change the execution context:

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // I create a Generator Object that tests the "this" binding.
  • function* genfunk() {
  •  
  • console.log( chalk.red.bold( "[Generator]" ) );
  • console.log( chalk.red( "[this]:" ), this );
  •  
  • }
  •  
  • // When we invoke the generator function, we can use the .call() / .apply() methods to
  • // change the execution context of the resultant Generator Object. Once we do this, all
  • // calls to .next(), .throw(), and .return() will execute with the given .this binding.
  • var generator = genfunk.call({
  • description: "I was bound to the generator using .call()."
  • });
  •  
  • // Let's test to see what ".this" binding is in place.
  • generator.next();

As you can see, rather than calling the generator function directly, we're indirectly calling it through the use of the .call() Function method. This sets up our provided object as part of the execution context of the Generator; and, when we run the above code, we get the following terminal output:


 
 
 

 
 Changing the execution context of a Generator in JavaScript and Node.js. 
 
 
 

As you can see, by using .call() to change the execution context of the generator function, it creates a Generator Object that is bound to the given context. As such, when we log "this" to the console, we get the object that was passed into the .call() method.

Now that we see that we can use .call() - or .apply() - to change the execution context of the resultant Generator Object, it got me thinking about the prototype chain. The Generator object might have a touch of magic to it; but, the prototype chain does not. As such, I wanted to see if I could use the prototype chain to dynamically change the execution context even after the Generator object was created.

To do this, I am going to provide an "intermediary context" as the "this" binding when invoking the generator function. This locks the intermediary context in as part of the execution context of the Generator object. But, we can use the ES6 Object.setPrototypeOf() method to dynamically change the prototype of the "intermediary context", thereby changing the prototypal inheritance of the Generator at execution time. This means that we can alter the prototype chain of the Generator in between our calls to .next():

CAUTION: This is just a fun experiment meant to learn me some things about JavaScript and ES6. I am not recommending that you do this. In fact, this will not behave as you expect since any WRITES to the prototype chain happen at the lowest level, which is probably not what you want.

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // I am a generator functions that yields 3 times, requiring 4 calls to .next() in order
  • // to be fully consumed.
  • function* genfunk() {
  •  
  • console.log( chalk.red.bold( "(1)" ), "this.description:", this.description );
  • yield;
  •  
  • console.log( chalk.red.bold( "(2)" ), "this.description:", this.description );
  • yield;
  •  
  • console.log( chalk.red.bold( "(3)" ), "this.description:", this.description );
  • yield;
  •  
  • console.log( chalk.red.bold( "(4)" ), "this.description:", this.description );
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // Once the Generator Object is created by the generator function, we can't change the
  • // execution context of it directly. However, we can set the execution context of the
  • // Generator Object at creation time. This means that we can use PROTOTYPAL INHERITANCE
  • // to expose a portion of the prototype chain that we can change. This "proxy context"
  • // will be the execution context of the Generator Object throughout the entire execution;
  • // however, we'll be able to CHANGE THE PROTOTYPE OF THE PROXY in order to dynamically
  • // change the context of the Generator.
  • // --
  • // CAUTION: Due to the way prototypal inheritance works, if you SET A VALUE INTO THE
  • // PROTOTYPE CHAIN, it will be stored in the closest link in the prototype chain. As
  • // such, you can read from the higher-up prototypes, but, you can't write to them.
  • var proxyContext = Object.create( null );
  •  
  • // Create the Generator Object using the "proxy" as the execution context (ie, the
  • // "proxyContext" will be used as the "this" binding internally).
  • var generator = genfunk.call( proxyContext );
  •  
  • // Now, let's inject a convenience method into the Generator Object that will allows us
  • // to change the prototype chain at execution time.
  • generator.bindTo = function( newContext ) {
  •  
  • Object.setPrototypeOf( proxyContext, newContext );
  •  
  • };
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // These are the various prototypal contexts that we'll be using to alter the execution
  • // binding of the Generator as it starts to yield values.
  • var contextA = {
  • description: "I am context A."
  • };
  • var contextB = {
  • description: "I am context B."
  • };
  • var contextC = {
  • description: "I am context C."
  • };
  •  
  • // Now that our Generator Object has been built using a this-context that can be swapped
  • // at any time, let's try consuming the values, changing the context between each of the
  • // .next() invocations.
  • generator.next();
  •  
  • generator.bindTo( contextA );
  • generator.next();
  •  
  • generator.bindTo( contextB );
  • generator.next();
  •  
  • generator.bindTo( contextC );
  • generator.next();

As you can see, I am still using the .call() method to change the execution context of the Generator object when it is created from the generator function. But, this time, I'm also injecting a .bindTo() method which will dynamically change the prototype chain of the Generator object. This allows us to change the prototype chain traversal in between calls to .next(). And, when we run the above code, we get the following output:

AGAIN CAUTION: This will change the READs but not the WRITEs on the prototype chain.


 
 
 

 
 Changing the execution context of a Generator in JavaScript and Node.js. 
 
 
 

As you can see, when we change the prototype chain in between calls to .next(), we change the value that is read out of "this.description".

Changing the prototype chain is just a fun experiment, but it's not something that I think solves an actual problem. Setting the initial execution context and "this" binding of the Generator Object using the .call() method on the generator function, however, is extremely helpful, especially when we start mixing the use of generators in with the use of "classes" in Node.js.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

@All,

I just wanted to do a quick follow-up on this post. In the current version, as I stated VERY CLEARLY, messing with the Prototype chain will work for Reads, but not for Writes. Well, if we use the ES6 Proxy object as part of the Generator execution context, we can successfully change both Reads and Writes:

https://www.bennadel.com/blog/3262-using-proxy-objects-to-dynamically-change-the-this-binding-within-a-generator-in-node-js.htm

To be clear, I can't think of a reason to do this; but, it was fun to play around with Proxy objects for the firs time. And, the more I can wrap my head around Generator objects, the better.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.