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.