I am using bluebird npm package for handling asynchronous workloads in my application. Lets assume that processAllItems
function is used to trigger processing of all the items. Each one of the items from the list, needs to be processed using function processOneItem
which processes them one by one. It internally calls few functions from the same class. Bluebird library's .map
function is used to manage concurrency of execution here. So, my code looks something like this.
const Bluebird = require('bluebird');
module.exports = function (app) {
return {
doSomething: function () {
return new Promise(function (resolve, reject) {
// do something and resolve
});
},
processOneItem: function (item) {
let self = this;
return new Promise(function (resolve, reject) {
// blah
self.doSomething() //self is undefined
.then(function () {
//do something else and so on...
})
});
},
processAllItems: function () {
const self = this;
return new Promise(function (resolve, reject) {
// processOneItem function is to be called for an array of data
// lets assume
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Bluebird.map(array, self.processOneItem, { concurrency: 5 })
.then(function () {
return resolve();
})
.catch(function (error) {
return reject(error);
});
});
}
};
};
Since functions doSomething
, processOneItem
and processAllItems
are keys of the same object, they need to be accessed by the this
object, which is assigned to self
variable. This all makes sense from javascript perspective so far. But when I execute this code, I get error saying Cannot read property 'doSomething'
on line self.doSomething()
inside function processOneItem
. How is it possible that the reference to this
object is lost? Am I doing something wrong here?
One thing I noticed, if I wrap the iteratee function inside another unnamed function, this seems to work perfectly fine.
const Bluebird = require('bluebird');
module.exports = function (app) {
return {
doSomething: function () {
return new Promise(function (resolve, reject) {
// do something and resolve
});
},
processOneItem: function (item) {
let self = this;
return new Promise(function (resolve, reject) {
// blah
self.doSomething() //self reference is maintained, works perfectly fine.
.then(function () {
//do something else and so on...
})
});
},
processAllItems: function () {
const self = this;
return new Promise(function (resolve, reject) {
// processOneItem function is to be called for an array of data
// lets assume
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Bluebird.map(array, function (record) { return self.processOneItem(record); }, { concurrency: 5 })
.then(function () {
return resolve();
})
.catch(function (error) {
return reject(error);
});
});
}
};
};
How is it possible that when the line
Bluebird.map(array, self.processOneItem, { concurrency: 5 })
is replaced with line
Bluebird.map(array, function (record) { return self.processOneItem(record); }, { concurrency: 5 })
the reference to this
object is maintained inside the iteratee function and code works perfectly fine.
This is a question of javascript binding. Here is a simplification of your case above that yield the same result regardless of bluebird.
const obj = {
a: function(){
console.log('A THIS!', this)
},
b: function(){
console.log('B THIS!', this)
}
}
function c_function(paramsFunction){
paramsFunction();
}
obj.a() // A THIS!, {a: function(), b: function()}
obj.b() // b THIS!, {a: function(), b: function()}
c_function(obj.a) //"A THIS!" global {Buffer: function(), clearImmediate: function(), clearInterval: function(), clearTimeout: function(), …}
c_function(function() { obj.a()}) // A THIS!, {a: function(), b: function()}
The this
of the javascript for a function execution is binded at the execution of that function.
So when calling
obj.a()
It check for the this rules and find it was called on an object so the this
will be obj
.
c_function(obj.a)
c_function receive a function as an argument. When it execute the function it try to bind the this. As c_function scope is concerned, the function is not called on an object. Therefore it will bind the this according to the rules. Here c_function
is binded to the global this
and executing paramsFunction()
the binding will be the same.
c_function(function() { obj.a()}) // Or
c_function(() => { obj.a()})
All the knowledge of the 2 previous are combine. The important takeaway is that at the moment of the call you are calling from obj
EXTRA
Note that the map function may call your function with a bind
, call
or apply
. This will affect the scope of the executed function.
function c_function_binding(paramsFunction) {
paramsFunction.call({}, paramsFunction)
}
c_function_binding(obj.a) // "A THIS!" {}
I was not able to find it, but a few years ago Kyle Simpson had a course or a workshop that explained in detail the scope binding in javascript and all its quirks.