Reputation: 4306
I am an heavy javascript prototypes and promises user.
My problem is that I need to use .bind(this)
to set the right context about every time I thenify my promises.
Here is a sample code showing the problem (jsbin):
var Q = require('Q');
var AsyncCounter = function(initialValue){
this.value = initialValue;
};
AsyncCounter.prototype.increment=function(){
return Q.fcall(function(){
return ++this.value;
}.bind(this));
};
AsyncCounter.prototype.decrement=function(){
return Q.fcall(function(){
return --this.value;
}.bind(this));
};
var counter = new AsyncCounter(10);
counter.increment()
.then(function(incrementedValue){
console.log('incremented value:', incrementedValue)
})
.then(counter.decrement.bind(counter))
.then(function(decrementedValue){
console.log('decremented value:', decrementedValue)
});
See how often I have to rely on bind()
? I find it too unelegant.
I do know about petkaantonov/bluebird promise library and its very useful bind(scope)
function that propagate the scope on callbacks but I feel like there is a better, native way to do it.
Does anybody have a proper way to do it?
Upvotes: 3
Views: 3335
Reputation: 93
If you're using ES6 you could use arrow functions and use the lexical scope.
AsyncCounter.prototype.increment = () => {
return Q.fcall(() => ++this.value);
};
Upvotes: 1
Reputation:
The best solution in my opinion is to add a context
method to promises to specify the this
for function calls in future then
invocations. This is something I seem to recall having seen in some libraries, can't remember where, and implemented in my own as well. The idea is to write
promise.context(this).then(myfunc)
I don't know Q, so I don't know if it would be easy or even possible to extend it/customize it to have this behavior. Here is a toy implementation of this idea using native JS promises:
Promise.prototype.context = function (ctxt) { this.ctxt = ctxt; return this; }
Promise.prototype.then = (function() {
var old_then = Promise.prototype.then;
return function(fulfill, reject) {
return old_then.call(this,
fulfill && fulfill.bind && fulfill.bind(this.ctxt),
reject && reject.bind && reject.bind(this.ctxt)
);
};
}());
Actually, you can often write your code so that it doesn't end up littered with context
calls, by doing it once where you create the promise. So:
var MyTrip = {
gotoMars: function() { return InterPlanetaryTrip(Mars).context(this); },
report: function() { console.log("I'm back, Mom!"); },
go: function() { gotoMars.then(this.report).then(this.drink); }
};
This approach saved me boundless aggravation and has zero downsides that I can see. I cannot see a more "native" way to do this, whatever that might mean.
As a more direct answer to your question, you could possibly introduce a small bit of sugar--a method which binds and calls Q.fcall
in one fell swoop, as follows:
AsyncCounter.prototype.fcall = function(fn) {
return Q.fcall(fn.bind(this));
};
AsyncCounter.prototype.increment = function() {
return this.fcall(function() {
return ++this.value;
});
};
Upvotes: 1
Reputation: 19298
Remember when you learned about Foo.prototype.bar=function(){...}
and turned your back on defining methods inside the constructor as this.bar = function(){...}
? The reasons were speed, and efficiency of memory.
Well now you have good reason to turn the clock back and sacrifice speed/efficiency in order to achieve "detachable" methods - ie methods that are specific to their instance of the constructor with this
ready bound, which is exactly what you want.
If the methods are called many times, then the inefficiency of construction will be more than compensated for by not having to use bind()
when referring to the function. Plus you get the syntactic convenience you seek.
Here's a version of your example re-jigged to better demonstrate the syntactic convenience :
var AsyncCounter = function(value){
this.increment = function(){
return Q.fcall(function(){
return console.log("++value: " + ++value);
}.bind(this));
}.bind(this);
this.decrement = function(){
return Q.fcall(function(){
return console.log("--value: " + --value);
}.bind(this));
}.bind(this);
};
var counter = new AsyncCounter(10);
counter.increment()
.then(counter.decrement)
.then(counter.increment);
In summary, you need to do two things :
this
to the methods.Upvotes: 4
Reputation: 2954
I think another way to do it is by taking advantage of closures. Every callback that is used in the promise can contain references to some variables of the root scope where it was created.
Normally what most of the people do is to create a variable called self
that maintans a reference to the outer this
. So wherever you execute the function of the promise, the closure will maintain the reference and therefore be able to access the scope.
AsyncCounter.prototype.increment=function(){
var self = this;
return Q.fcall(function(){
return ++self .value;
});
};
Honestly, I'm not sure which one is more elegant. I'm not sure if there are any other ways of doing what you want without using this technique or the bind
method.
Upvotes: 1