Search code examples
node.jsclassasynchronousmethodsscope

Chaining async class methods, Where and Why does the Scope Change?


I wrote a class module that has async methods, and now im trying to chain the methods using .then()

the scope of 'this' changes somewhere along the line and i then have to bind the function back to its instance to get the proper scope back.

class.js

class Asdf {
  #asdf;
  constructor(){
    this.#asdf = 'asdf'
  }
  async funcOne(){
    // h.funcOne().then // works fine
    return this.#asdf
  }
  async funcTwo(a){
    // .then(h.funcTwo) // this.funcThree becomes undefined?
    // where does the scope change??
    var x = await this.funcThree(a)
    return x
  }
  async funcThree(a){
    return a
  }
}
module.exports = Asdf

script.js

var h = new (require('./class.js'));

h.funcOne().then(h.funcTwo) // scope is changed, code breaks, claiming undefined

h.funcOne().then(h.funcTwo.bind(h)) // works because we re-bind the scope

// Where and Why does the Scope Change??

Why does the scope change using .then? and can I prevent it?


Solution

  • This is not about scope in the literal definition. It's about the binding of the this value when a function is called in different ways.

    The issue is that when you pass h.funcTwo as a function argument, it reaches into the h object and grabs a reference to funcTwo and passed JUST a reference to that function that will be called without any reference to h and will not be associated with h at all. That means that this inside that call to funcTwo() will be undefined because it wasn't called as h.funcTwo().

    So, as you found, can use .bind() to instead pass a function stub that will call it as h.funcTwo() rather than just as funcTwo().

    You can see a simpler example here:

    "use strict";
    
    const x = {
        f: function() { console.log(this)},
        g: "it's me",
    };
    
    x.f();    // properly logs the x object
    
    let y = x.f;
    y();      // logs undefined since the binding to x was lost
              // and f() was called all by itself without 
              // any reference to x
    
     let z = x.f.bind(x);      // properly logs the x object
     z();