Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.".

like image 437
Sergio Avatar asked Jan 18 '17 17:01

Sergio


People also ask

Is Promise a callback?

Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function.

Why Promise is used instead of callback?

They can handle multiple asynchronous operations easily and provide better error handling than callbacks and events. In other words also, we may say that, promises are the ideal choice for handling multiple callbacks at the same time, thus avoiding the undesired callback hell situation.

Is Promise better than callback?

A callback function is passed as an argument to another function whereas Promise is something that is achieved or completed in the future. In JavaScript, a promise is an object and we use the promise constructor to initialize a promise.

Do promises pause execution?

Promises Can Be Paused Just because you are already executing inside a then() function, doesn't mean you can't pause it to complete something else first. To pause the current promise, or to have it wait for the completion of another promise, simply return another promise from within then() .


2 Answers

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.

like image 97
nem035 Avatar answered Oct 28 '22 14:10

nem035


This has nothing to do with Promises, but rather the context in which this is called.

Case 1:

this.method(); // Foo

Here method is a function defined within the Foo class, so this is evaluated as the object that triggered the function, which is this in this.method. Hence - Foo is displayed.

Case 2:

Promise.resolve().then(() => console.log('inline arrow function', this)); // Foo

Arrow functions are a feature of ES6, whose unique property is that the this context in the enclosing context where it is defined. The function was called in a context where this === Foo so that's what's displayed.

Case 3:

Promise.resolve().then(normal); // window
Promise.resolve().then(arrow); // window

The arrow function retains its context as the window since it's an arrow function, and the normal function is evaluated without a context, in which this is evaluated to window when not in strict mode.

Case 4:

Promise.resolve().then(strictFunction); // undefined

Since strict mode is requested within the body of this function, which is declared on the window, this is evaluated to undefined.

Case 5:

Promise.resolve().then(this.method); // undefined <-- why?

In this spec, it is defined that all Class code is strict code:

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

like image 44
Omri Aharon Avatar answered Oct 28 '22 15:10

Omri Aharon