Reference loss in function call

Asked

Viewed 234 times

13

Setting:

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:

In the constructor the parameters are received and assigned to the this also occurring the function call save.

During function save the values of this are extracted in const and the service save is executed by returning the function onSuccess, or, in the event of failure, the function onerror.

Problem:

The functions Onsuccess and onerror are not executed upon return from service (appear as "Undefined" during TDD execution), by reference loss.

A solution found:

Call the job onSuccess and onerror with Arrow Function as follows:

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

The doubts are:

  • Why did this reference loss occur?
  • Why the use of Arrow functions solved the problem?
  • There would be another way to circumvent this reference loss in the above example?
  • Good afternoon Dorival, just to clarify: the functions onSuccess and onError are not being executed, or are executed without reference to the class?

  • Good afternoon @mrlew, the functions are not executed in the first case due to lack of reference, which does not happen in the second with the use of Arrow Function, so in the second case they perform normally. But there are cases where functions can be performed without reference?

  • 1

    Interesting question! Related to: http://answall.com/a/108745/129. I am in a hurry but I will reply later.

  • Beauty! I look forward to and thank you for sharing the link Sergio

  • @Dorivalzanetto your service is returning a Promise as per the document on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise ? Because I did a test and the result was expected according to the original scenario.

  • Two other solutions, if you are using Babel enabling the stage-2 of the new proposals. https://github.com/andreypopp/autobind-decorator and https://github.com/tc39/proposal-class-public-fields

  • @Dorivalzanetto I left reply and I was giving a touch. It was this information that you were looking for?

  • Yes @Sergio, was very enlightening. Congratulations for the contents presented in the reply and thank you!

  • I am using Babel as transpile yes and I will give one in the links, thanks @Gabrielkatakura!

  • One more solution: http://blog.jeremyfairbank.com/javascript/javascript-es7-function-bind-syntax/

Show 5 more comments

2 answers

6


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

Abridged:

a) Why this reference loss occurred?

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

b) Why the use of Arrow functions has solved the problem?

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

c) There would be another way to circumvent this reference loss in the above example?

Yeah, you can use the .bind() to force the execution context. O .bind() creates a copy of the function, without calling it, and when it is called runs with the context set in .bind(contexto).

Example:

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

Longer explanation:

The execution context of a function depends on several things. The general rule is that the execution context is the object/class to which the function belongs. I talked about it in another question. In the case of normal functions an example may 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');

Despite the function teste run in a specific context, log will take place in the context of window.

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

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

  • if you are in strict mode

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

  • if you are not in strict mode

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

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

new Classe();

b) Why the use of Arrow functions has solved the problem?

This is one of the advantages and differences between functions we already know, declared with function. To Arrow Function will always have as the context of execution the surrounding 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

That is to say, Arrow functions use the context around them, while "old-fashioned" functions receive the context of the object to which they belong.

-2

The scenario code is correct, there should be no reference loss if the service correctly implements Promise according to the es6. Thus to use the then(), the save() method of the service class must return a Promise object. The documentation of the Mozilla has a good explanation. There may probably be some problem in implementing the Promise object returned by the service or in calling the onSuccess or onerror function.

Example:

class MyService {

  save(id) {

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

  }

}

Below a link with the example working that works if the browser implements the es6.

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

  • The problem is that these methods onSucess and onError are called with an execution context that is no longer the class. Take a look here: https://jsfiddle.net/g5nj3cn9/4/ and the error this generates due to lack of .bind(this)

  • @Sergio agrees with you, if you had specified the methods onSuccess and onerror. It turns out that the developer with less knowledge would see as mandatory the need to use the Arrow functions in any then() of a Promise. The submitted code has no need to add this resource. I believe the question should be edited for better clarification of users.

  • @Dorivalzanetto is broken even, to show that the this is wrong. I did so broken on purpose, just for example.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.