Reference loss in function call

11

Scenery:

class ClassName {

    constructor(service, params) {
        this.service = service;
        this.params = params;

        this.save(params.id);
    }

    save(id) {
        const { service, onSuccess, onError } = this;

        return service.save({ id }).then(onSuccess, onError);
    }

    onSuccess() {
        //do something
    }

    onError() {
        //do something
    }
}

Explanation:

The contructor parameters are received and assigned to this and the save During the save function, the this values are extracted in const and the save service is performed by returning the onSuccess function, or, on failure, the onError function.

Problem:

The OnSuccess and onError functions do not run after the service returns (they appear as "undefined" during TDD execution) p>

One solution found:

Call the onSuccess and onError functions with arrow function as follows:

.then(() => this.onSuccess(), () => this.onError());

The questions are:

  • Why did this reference loss occur?
  • Why did arrow functions solve the problem?
  • Would there be another way to circumvent this reference loss in the example above?
asked by anonymous 18.01.2017 / 14:08

2 answers

5

This is a common problem, which can be resolved with arrow functions or with .bind() .

Summarized:

a) Why did this reference loss occur?

When we run a function as a callback of a Promise the execution context ( this ) changes to undefined or the global object ( window in the browser).

b) Why did the use of arrow functions remedy the problem?

This is one of the novelties (new possibility) with arrow function , it runs in the context of where it is declared.

c) Would there be another way to circumvent this reference loss in the above example?

Yes, you can use .bind() to force the context of execution. The .bind() creates a copy of the function, without invoking it, and when it is called it runs with the defined context in .bind(contexto) .

Example:

return service.save({ id }).then(this.onSuccess.bind(this), this.onError.bind(this));

More elongated explanation:

The execution context of a function depends on several things. It is generally said that the execution context is the object / class to which the function belongs. I talked about this in another question . In the case of normal functions an example can be:

Example:

function teste() {
  console.log('contexto de teste é foo?:', this == 'foo');
  log();
}

function log() {
  console.log('contexto de log é foo?:', this == 'foo');
}

teste.call('foo');

Although the teste function runs in its own context, log will run in the window execution context.

Functions passed as callback to a Promise are executed in another execution context. So, passing a function as callback to .then() does not guarantee the execution context. This does not apply to arrow functions declared inline.

Promise has two modes of operation with respect to the callback execution context:

  • If you are in strict mode

If we are in strict mode or the function used as callback implements strict mode , then this will be undefined . This is your case, because ES6 class methods always run in strict mode .

  • if it is not in strict mode

If we are not in strict mode nor the function used as callback implements strict mode , then the execution context will be the global object, window in the browser case.

class Classe {
  constructor(){
    this.teste();
    Promise.resolve().then(this.teste);
    }
  teste() {
    console.log(this);
  }
}

new Classe();

b) Why did the use of arrow functions remedy the problem?

This is one of the advantages and differences between functions we already know, declared with function . The arrow function will always have the surrounding context as the context where it is inserted.

Example:

class Classe {
    constructor() {
        const logA = () => {
            console.log('logA', this);
        };
        const logB = function() {
            console.log('logB', this);
        }
        logA();
        logB();
    }
}

const logC = () => {
    console.log('logC', this);
};
const logD = function() {
    console.log('logD', this);
}
new Classe();
logC();
logD();

In this example ( jsFiddle ) the results are:

logA // dá Classe {}
logB // undefined
logC // window
logD // undefined

In other words, arrow functions use the context around it, while "old fashioned" functions get the context of the object they belong to.     

18.01.2017 / 18:37
-2

Scenario code is correct, there should be no referral loss if the service implements Promise correctly by standardizing es6 . So to use then (), the save () method of the service class must return a Promise object. The documentation for Mozilla has a good explanation. There may possibly be a problem in the implementation of the Promise object returned by the service or in the onSuccess or onError function call.

Example:

class MyService {

  save(id) {

     console.log("Saving "+id);
     return Promise.resolve("Record id "+id+" saved!");

  }

}

Below is a link to the example that works if the browser implements es6.

Note: See in this link the list of browsers implements the es6 specification. If the browser does not support the Promise specification it is possible to add a pollyfill.

    
18.01.2017 / 17:39