Search code examples
javascriptecmascript-6promisees6-promise

Promise.then execution context when using class methods as callback


Why does Promise.then passes execution context of undefined when using a class method as callback, and window when using a "normal function"?

Is the class method detached from its owning object/class? and why undefined and not window?

function normal() {
    console.log('normal function', this);
}
const arrow = () => {
    console.log('arrow function', this);
}

function strictFunction() {
    'use strict';
    console.log('strict function', this);
}

class Foo {
    test() {
        this.method(); // Foo
        Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo
        Promise.resolve().then(normal); // window
        Promise.resolve().then(arrow); // window
        Promise.resolve().then(strictFunction); // undefined
        Promise.resolve().then(this.method); // undefined <-- why?
    }

    method() {
        console.log('method', this);
    }
}

const F = new Foo();
F.test();

(jsFiddle)

I would expect the context of this.method to be lost but cannot understand why the different behavior between this.method and "normal" and arrow functions.

Is there a spec for this behavior? The only reference I found was Promises A+ refering to that "in strict mode this will be undefined inside; in sloppy mode, it will be the global object.".


Solution

  • The quote you have there tells you why:

    in strict mode this will be undefined inside; in sloppy mode, it will be the global object.

    The ES6 spec says that:

    All parts of a ClassDeclaration or a ClassExpression are strict mode code

    Therefore, because of strict mode, this within an unbound class method, will be undefined.

    class A {
      method() {
        console.log(this);
      }
    }
    
    const a = new A();
    a.method(); // A
    const unboundMethod = a.method;
    unboundMethod(); // undefined

    This is the same behavior you would get if you passed a normal function with strict mode because this binding is undefined by default in strict mode, not set to the global object.

    The reason normal and arrow have this as window is because they are not within the class and thus not wrapped in strict mode.


    As far as promises and the then method, it will just pass undefined as this but won't override already bound this.

    If you look at the PromiseReactionJob spec:

    The job PromiseReactionJob with parameters reaction and argument applies the appropriate handler to the incoming value, and uses the handler's return value to resolve or reject the derived promise associated with that handle.

    ...
    let handlerResult be Call(handler, undefined, «argument»).
    

    The second argument to Call is the this value, which is set to undefined.