The this
keyword is a source of headaches to people unfamiliar with the nuances of how JavaScript assigns its value. The value of this
corresponds to execution context, and not scope. In this case, waitForBufferingComplete
's execution context isn't the same as the context ensureBufferingHasFinished
is called with.
You have a few options on how to ensure you're accessing what you need.
Assign variables to outer scope
The age-old hack of assigning this
or properties thereof is quick and reliable in situations like yours where functions are nested within each other:
function ensureBufferingHasFinished() {
var that = this,
sp = this.serviceProvider;
return new Promise(function (resolve, reject) {
(function waitForBufferingComplete() {
//you can use "that.serviceProvider", or "sp"
if (!sp.getBufferingStatus()) {
alert("RESOLVED");
return resolve();
}
setTimeout(waitForBufferingComplete, 250);
})();
});
}
Use function.bind()
Calling bind
on a function forces it to have the execution context you explicitly give as its argument, and is more useful if you do not or cannot have your callbacks within the scope containing the value you need:
function ensureBufferingHasFinished() {
return new Promise(function (resolve, reject) {
(function waitForBufferingComplete() {
if (!this.serviceProvider.getBufferingStatus()) {
alert("RESOLVED");
return resolve();
}
setTimeout(waitForBufferingComplete.bind(this), 250);
}.bind(this))();
}.bind(this));
}
or
function ensureBufferingHasFinished() {
return new Promise(_resolveBufferingPromise.bind(this));
}
function _resolveBufferingPromise(resolve, reject) {
(function waitForBufferingComplete() {
if (!this.serviceProvider.getBufferingStatus()) {
alert("RESOLVED");
return resolve();
}
setTimeout(waitForBufferingComplete.bind(this), 250);
}.bind(this))();
}
You can also pass the serviceProvider
to the IIFE you made around waitForBufferingComplete
, though with your code structure you should only do this if you're supporting ES5-compatible browsers as setTimeout
didn't support passing additional parameters until then:
function ensureBufferingHasFinished() {
return new Promise(function (resolve, reject) {
(function waitForBufferingComplete(serviceProvider) {
if (!serviceProvider.getBufferingStatus()) {
alert("RESOLVED");
return resolve();
}
setTimeout(waitForBufferingComplete, 250, serviceProvider);
})(this.serviceProvider);
}.bind(this));
}
Use arrow functions (ES2015 or later)
If you're developing for platforms that support ES2015, that version introduced arrow functions which ignore execution context and retain the lexical scope of this
from its parent:
function ensureBufferingHasFinished() {
return new Promise((resolve, reject) => {
var waitForBufferingComplete = () => {
if (!this.serviceProvider.getBufferingStatus()) {
alert("RESOLVED");
return resolve();
}
setTimeout(waitForBufferingComplete, 250);
}
});
}
Read up on more about this
on MDN.