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.